1 /*
2  * This file Copyright (C) 2016 Mnemosyne LLC
3  *
4  * It may be used under the GNU GPL versions 2 or 3
5  * or any future license endorsed by Mnemosyne LLC.
6  *
7  */
8 
9 #include <string.h>
10 #include <time.h>
11 
12 #ifndef _WIN32
13 #include <sys/stat.h>
14 #endif
15 
16 #include "transmission.h"
17 #include "crypto-utils.h"
18 #include "error.h"
19 #include "error-types.h"
20 #include "file.h"
21 #include "log.h"
22 #include "platform.h"
23 #include "session-id.h"
24 #include "utils.h"
25 
26 #define SESSION_ID_SIZE 48
27 #define SESSION_ID_DURATION_SEC (60 * 60) /* expire in an hour */
28 
29 struct tr_session_id
30 {
31     char* current_value;
32     char* previous_value;
33     tr_sys_file_t current_lock_file;
34     tr_sys_file_t previous_lock_file;
35     time_t expires_at;
36 };
37 
generate_new_session_id_value(void)38 static char* generate_new_session_id_value(void)
39 {
40     char const pool[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
41     size_t const pool_size = sizeof(pool) - 1;
42 
43     char* buf = tr_new(char, SESSION_ID_SIZE + 1);
44 
45     tr_rand_buffer(buf, SESSION_ID_SIZE);
46 
47     for (size_t i = 0; i < SESSION_ID_SIZE; ++i)
48     {
49         buf[i] = pool[(unsigned char)buf[i] % pool_size];
50     }
51 
52     buf[SESSION_ID_SIZE] = '\0';
53 
54     return buf;
55 }
56 
get_session_id_lock_file_path(char const * session_id)57 static char* get_session_id_lock_file_path(char const* session_id)
58 {
59     char* lock_file_dir = tr_getSessionIdDir();
60     char* lock_file_path = tr_strdup_printf("%s/tr_session_id_%s", lock_file_dir, session_id);
61     tr_free(lock_file_dir);
62     return lock_file_path;
63 }
64 
create_session_id_lock_file(char const * session_id)65 static tr_sys_file_t create_session_id_lock_file(char const* session_id)
66 {
67     if (session_id == NULL)
68     {
69         return TR_BAD_SYS_FILE;
70     }
71 
72     char* lock_file_path = get_session_id_lock_file_path(session_id);
73     tr_sys_file_t lock_file;
74     tr_error* error = NULL;
75 
76     lock_file = tr_sys_file_open(lock_file_path, TR_SYS_FILE_READ | TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0600, &error);
77 
78     if (lock_file != TR_BAD_SYS_FILE)
79     {
80         if (tr_sys_file_lock(lock_file, TR_SYS_FILE_LOCK_EX | TR_SYS_FILE_LOCK_NB, &error))
81         {
82 #ifndef _WIN32
83             /* Allow any user to lock the file regardless of current umask */
84             fchmod(lock_file, 0644);
85 #endif
86         }
87         else
88         {
89             tr_sys_file_close(lock_file, NULL);
90             lock_file = TR_BAD_SYS_FILE;
91         }
92     }
93 
94     if (error != NULL)
95     {
96         tr_logAddError("Unable to create session lock file (%d): %s", error->code, error->message);
97         tr_error_free(error);
98     }
99 
100     tr_free(lock_file_path);
101     return lock_file;
102 }
103 
destroy_session_id_lock_file(tr_sys_file_t lock_file,char const * session_id)104 static void destroy_session_id_lock_file(tr_sys_file_t lock_file, char const* session_id)
105 {
106     if (lock_file != TR_BAD_SYS_FILE)
107     {
108         tr_sys_file_close(lock_file, NULL);
109     }
110 
111     if (session_id != NULL)
112     {
113         char* lock_file_path = get_session_id_lock_file_path(session_id);
114         tr_sys_path_remove(lock_file_path, NULL);
115         tr_free(lock_file_path);
116     }
117 }
118 
tr_session_id_new(void)119 tr_session_id_t tr_session_id_new(void)
120 {
121     tr_session_id_t const session_id = tr_new0(struct tr_session_id, 1);
122 
123     session_id->current_lock_file = TR_BAD_SYS_FILE;
124     session_id->previous_lock_file = TR_BAD_SYS_FILE;
125 
126     return session_id;
127 }
128 
tr_session_id_free(tr_session_id_t session_id)129 void tr_session_id_free(tr_session_id_t session_id)
130 {
131     if (session_id == NULL)
132     {
133         return;
134     }
135 
136     destroy_session_id_lock_file(session_id->previous_lock_file, session_id->previous_value);
137     destroy_session_id_lock_file(session_id->current_lock_file, session_id->current_value);
138 
139     tr_free(session_id->previous_value);
140     tr_free(session_id->current_value);
141 
142     tr_free(session_id);
143 }
144 
tr_session_id_get_current(tr_session_id_t session_id)145 char const* tr_session_id_get_current(tr_session_id_t session_id)
146 {
147     time_t const now = tr_time();
148 
149     if (session_id->current_value == NULL || now >= session_id->expires_at)
150     {
151         destroy_session_id_lock_file(session_id->previous_lock_file, session_id->previous_value);
152         tr_free(session_id->previous_value);
153 
154         session_id->previous_value = session_id->current_value;
155         session_id->current_value = generate_new_session_id_value();
156 
157         session_id->previous_lock_file = session_id->current_lock_file;
158         session_id->current_lock_file = create_session_id_lock_file(session_id->current_value);
159 
160         session_id->expires_at = now + SESSION_ID_DURATION_SEC;
161     }
162 
163     return session_id->current_value;
164 }
165 
tr_session_id_is_local(char const * session_id)166 bool tr_session_id_is_local(char const* session_id)
167 {
168     bool ret = false;
169 
170     if (session_id != NULL)
171     {
172         char* lock_file_path = get_session_id_lock_file_path(session_id);
173         tr_sys_file_t lock_file;
174         tr_error* error = NULL;
175 
176         lock_file = tr_sys_file_open(lock_file_path, TR_SYS_FILE_READ, 0, &error);
177 
178         if (lock_file == TR_BAD_SYS_FILE)
179         {
180             if (TR_ERROR_IS_ENOENT(error->code))
181             {
182                 tr_error_clear(&error);
183             }
184         }
185         else
186         {
187             if (!tr_sys_file_lock(lock_file, TR_SYS_FILE_LOCK_SH | TR_SYS_FILE_LOCK_NB, &error))
188             {
189 #ifndef _WIN32
190                 if (error->code == EWOULDBLOCK)
191 #else
192                 if (error->code == ERROR_LOCK_VIOLATION)
193 #endif
194                 {
195                     ret = true;
196                     tr_error_clear(&error);
197                 }
198             }
199 
200             tr_sys_file_close(lock_file, NULL);
201         }
202 
203         if (error != NULL)
204         {
205             tr_logAddError("Unable to open session lock file (%d): %s", error->code, error->message);
206             tr_error_free(error);
207         }
208 
209         tr_free(lock_file_path);
210     }
211 
212     return ret;
213 }
214