815 char *pwfile = util_concat_path(ENV_HOME, ".dav/secrets.crypt"); |
816 char *pwfile = util_concat_path(ENV_HOME, ".dav/secrets.crypt"); |
816 int ret = pwdstore_store(pwdstore, pwfile); |
817 int ret = pwdstore_store(pwdstore, pwfile); |
817 free(pwfile); |
818 free(pwfile); |
818 return ret; |
819 return ret; |
819 } |
820 } |
|
821 |
|
822 |
|
823 |
|
824 |
|
825 Repository* url2repo_s(sstr_t url, char **path) { |
|
826 *path = NULL; |
|
827 |
|
828 int s; |
|
829 if(sstrprefix(url, SC("http://"))) { |
|
830 s = 7; |
|
831 } else if(sstrprefix(url, SC("https://"))) { |
|
832 s = 8; |
|
833 } else { |
|
834 s = 1; |
|
835 } |
|
836 |
|
837 // split URL into repository and path |
|
838 sstr_t r = sstrsubs(url, s); |
|
839 sstr_t p = sstrchr(r, '/'); |
|
840 r = sstrsubsl(url, 0, url.length-p.length); |
|
841 if(p.length == 0) { |
|
842 p = sstrn("/", 1); |
|
843 } |
|
844 |
|
845 Repository *repo = get_repository(r); |
|
846 if(repo) { |
|
847 *path = sstrdup(p).ptr; |
|
848 } else { |
|
849 // TODO: who is responsible for freeing this repository? |
|
850 // how can the callee know, if he has to call free()? |
|
851 repo = calloc(1, sizeof(Repository)); |
|
852 repo->name = strdup(""); |
|
853 repo->decrypt_content = true; |
|
854 repo->verification = true; |
|
855 repo->authmethods = CURLAUTH_BASIC; |
|
856 if(url.ptr[url.length-1] == '/') { |
|
857 repo->url = sstrdup(url).ptr; |
|
858 *path = strdup("/"); |
|
859 } else if (sstrchr(url, '/').length > 0) { |
|
860 // TODO: fix the following workaround after |
|
861 // fixing the inconsistent behavior of util_url_*() |
|
862 repo->url = util_url_base_s(url); |
|
863 sstr_t truncated = sstrdup(url); |
|
864 *path = strdup(util_url_path(truncated.ptr)); |
|
865 free(truncated.ptr); |
|
866 } else { |
|
867 repo->url = sstrdup(url).ptr; |
|
868 *path = strdup("/"); |
|
869 } |
|
870 } |
|
871 |
|
872 return repo; |
|
873 } |
|
874 |
|
875 Repository* url2repo(char *url, char **path) { |
|
876 return url2repo_s(sstr(url), path); |
|
877 } |
|
878 |
|
879 static int decrypt_secrets(CmdArgs *a, PwdStore *secrets) { |
|
880 if(cmd_getoption(a, "noinput")) { |
|
881 return 1; |
|
882 } |
|
883 |
|
884 char *ps_password = NULL; |
|
885 if(secrets->unlock_cmd && strlen(secrets->unlock_cmd) > 0) { |
|
886 UcxBuffer *cmd_out = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND); |
|
887 if(!util_exec_command(secrets->unlock_cmd, cmd_out)) { |
|
888 // command successful, get first line from output without newline |
|
889 // and use that as password for the secretstore |
|
890 size_t len = 0; |
|
891 for(size_t i=0;i<=cmd_out->size;i++) { |
|
892 if(i == cmd_out->size || cmd_out->space[i] == '\n') { |
|
893 len = i; |
|
894 break; |
|
895 } |
|
896 } |
|
897 if(len > 0) { |
|
898 ps_password = malloc(len + 1); |
|
899 memcpy(ps_password, cmd_out->space, len); |
|
900 ps_password[len] = 0; |
|
901 } |
|
902 } |
|
903 ucx_buffer_free(cmd_out); |
|
904 } |
|
905 |
|
906 if(!ps_password) { |
|
907 ps_password = util_password_input("Master password: "); |
|
908 if(!ps_password) { |
|
909 return 1; |
|
910 } |
|
911 } |
|
912 |
|
913 if(pwdstore_setpassword(secrets, ps_password)) { |
|
914 fprintf(stderr, "Error: cannot create key from password\n"); |
|
915 return 1; |
|
916 } |
|
917 if(pwdstore_decrypt(secrets)) { |
|
918 fprintf(stderr, "Error: cannot decrypt secrets store\n"); |
|
919 return 1; |
|
920 } |
|
921 return 0; |
|
922 } |
|
923 |
|
924 typedef struct CredLocation { |
|
925 char *id; |
|
926 char *location; |
|
927 } CredLocation; |
|
928 |
|
929 static int cmp_url_cred_entry(CredLocation *e1, CredLocation *e2, void *n) { |
|
930 return strcmp(e2->location, e1->location); |
|
931 } |
|
932 |
|
933 static void free_cred_location(CredLocation *c) { |
|
934 // c->id is not a copy, therefore we don't have to free it |
|
935 free(c->location); |
|
936 free(c); |
|
937 } |
|
938 |
|
939 static int get_stored_credentials(CmdArgs *a, char *credid, char **user, char **password) { |
|
940 if(!credid) { |
|
941 return 0; |
|
942 } |
|
943 |
|
944 PwdStore *secrets = get_pwdstore(); |
|
945 if(!secrets) { |
|
946 fprintf(stderr, "Error: no secrets store available\n"); |
|
947 return 0; |
|
948 } |
|
949 |
|
950 if(pwdstore_has_id(secrets, credid)) { |
|
951 if(!secrets->isdecrypted) { |
|
952 if(decrypt_secrets(a, secrets)) { |
|
953 return 0; |
|
954 } |
|
955 } |
|
956 |
|
957 PwdEntry *s_cred = pwdstore_get(secrets, credid); |
|
958 if(s_cred) { |
|
959 *user = s_cred->user; |
|
960 *password = s_cred->password; |
|
961 return 1; |
|
962 } |
|
963 } else { |
|
964 fprintf(stderr, "Error: credentials id '%s' not found\n", credid); |
|
965 } |
|
966 |
|
967 return 0; |
|
968 } |
|
969 |
|
970 |
|
971 static int get_location_credentials(CmdArgs *a, Repository *repo, char *path, char **user, char **password) { |
|
972 PwdStore *secrets = get_pwdstore(); |
|
973 if(!secrets) { |
|
974 return 0; |
|
975 } |
|
976 |
|
977 /* |
|
978 * The list secrets->location contains urls or repo names as |
|
979 * location strings. We need a list, that contains only urls |
|
980 */ |
|
981 UcxList *locations = NULL; |
|
982 UCX_FOREACH(elm, secrets->locations) { |
|
983 PwdIndexEntry *e = elm->data; |
|
984 |
|
985 UCX_FOREACH(loc, e->locations) { |
|
986 char *path; |
|
987 Repository *r = url2repo(loc->data, &path); |
|
988 CredLocation *urlentry = calloc(1, sizeof(CredLocation)); |
|
989 urlentry->id = e->id; |
|
990 urlentry->location = util_concat_path(r->url, path); |
|
991 locations = ucx_list_append(locations, urlentry); |
|
992 } |
|
993 } |
|
994 // the list must be sorted |
|
995 locations = ucx_list_sort(locations, (cmp_func)cmp_url_cred_entry, NULL); |
|
996 |
|
997 // create full request url string and remove protocol prefix |
|
998 sstr_t req_url_proto = sstr(util_concat_path(repo->url, path)); |
|
999 sstr_t req_url = req_url_proto; |
|
1000 if(sstrprefix(req_url, S("http://"))) { |
|
1001 req_url = sstrsubs(req_url, 7); |
|
1002 } else if(sstrprefix(req_url, S("https://"))) { |
|
1003 req_url = sstrsubs(req_url, 8); |
|
1004 } |
|
1005 |
|
1006 // iterate over sorted locations and check if a location is a prefix |
|
1007 // of the requested url |
|
1008 char *id = NULL; |
|
1009 int ret = 0; |
|
1010 UCX_FOREACH(elm, locations) { |
|
1011 CredLocation *cred = elm->data; |
|
1012 sstr_t cred_url = sstr(cred->location); |
|
1013 |
|
1014 // remove protocol prefix |
|
1015 if(sstrprefix(cred_url, S("http://"))) { |
|
1016 cred_url = sstrsubs(cred_url, 7); |
|
1017 } else if(sstrprefix(cred_url, S("https://"))) { |
|
1018 cred_url = sstrsubs(cred_url, 8); |
|
1019 } |
|
1020 |
|
1021 if(sstrprefix(req_url, cred_url)) { |
|
1022 id = cred->id; |
|
1023 break; |
|
1024 } |
|
1025 } |
|
1026 |
|
1027 // if an id is found and we can access the decrypted secret store |
|
1028 // we can set the user/password |
|
1029 if(id && (secrets->isdecrypted || !decrypt_secrets(a, secrets))) { |
|
1030 PwdEntry *cred = pwdstore_get(secrets, id); |
|
1031 if(cred) { |
|
1032 *user = cred->user; |
|
1033 *password = cred->password; |
|
1034 ret = 1; |
|
1035 } |
|
1036 } |
|
1037 |
|
1038 free(req_url_proto.ptr); |
|
1039 ucx_list_free_content(locations, (ucx_destructor)free_cred_location); |
|
1040 ucx_list_free(locations); |
|
1041 |
|
1042 return ret; |
|
1043 } |
|
1044 |
|
1045 DavSession* connect_to_repo(DavContext *ctx, Repository *repo, char *path, dav_auth_func authfunc, CmdArgs *a) { |
|
1046 char *user = repo->user; |
|
1047 char *password = repo->password; |
|
1048 |
|
1049 if(!user && !password) { |
|
1050 if(!get_stored_credentials(a, repo->stored_user, &user, &password)) { |
|
1051 get_location_credentials(a, repo, path, &user, &password); |
|
1052 } |
|
1053 } |
|
1054 |
|
1055 DavSession *sn = dav_session_new_auth(ctx, repo->url, user, password); |
|
1056 sn->flags = get_repository_flags(repo); |
|
1057 sn->key = dav_context_get_key(ctx, repo->default_key); |
|
1058 curl_easy_setopt(sn->handle, CURLOPT_HTTPAUTH, repo->authmethods); |
|
1059 curl_easy_setopt(sn->handle, CURLOPT_SSLVERSION, repo->ssl_version); |
|
1060 if(repo->cert) { |
|
1061 curl_easy_setopt(sn->handle, CURLOPT_CAINFO, repo->cert); |
|
1062 } |
|
1063 if(!repo->verification || cmd_getoption(a, "insecure")) { |
|
1064 curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYPEER, 0); |
|
1065 curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYHOST, 0); |
|
1066 } |
|
1067 if(!cmd_getoption(a, "noinput")) { |
|
1068 dav_session_set_authcallback(sn, authfunc, repo); |
|
1069 } |
|
1070 return sn; |
|
1071 } |
|
1072 |
|
1073 int request_auth(DavSession *sn, void *userdata) { |
|
1074 Repository *repo = userdata; |
|
1075 |
|
1076 char *user = NULL; |
|
1077 char ubuf[256]; |
|
1078 if(repo->user) { |
|
1079 user = repo->user; |
|
1080 } else { |
|
1081 fprintf(stderr, "User: "); |
|
1082 fflush(stderr); |
|
1083 user = fgets(ubuf, 256, stdin); |
|
1084 } |
|
1085 if(!user) { |
|
1086 return 0; |
|
1087 } |
|
1088 |
|
1089 char *password = util_password_input("Password: "); |
|
1090 if(!password || strlen(password) == 0) { |
|
1091 return 0; |
|
1092 } |
|
1093 |
|
1094 size_t ulen = strlen(user); |
|
1095 if(user[ulen-1] == '\n') { |
|
1096 user[ulen-1] = '\0'; |
|
1097 } |
|
1098 |
|
1099 dav_session_set_auth(sn, user, password); |
|
1100 free(password); |
|
1101 |
|
1102 return 0; |
|
1103 } |