1 /*
2
3 Copyright (c) 2010 Samuel Lidén Borell <samuel@kodafritt.se>
4
5 Permission is hereby granted, free of charge, to any person obtaining a copy
6 of this software and associated documentation files (the "Software"), to deal
7 in the Software without restriction, including without limitation the rights
8 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 copies of the Software, and to permit persons to whom the Software is
10 furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 THE SOFTWARE.
22
23 */
24
25 #include "config.h"
26
27 #include <glib.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include "irc.h"
31 #include "fish.h"
32 #include "keystore.h"
33 #include "plugin_hexchat.h"
34
35
36 static char *keystore_password = NULL;
37
38
39 /**
40 * Opens the key store file: ~/.config/hexchat/addon_fishlim.conf
41 */
getConfigFile(void)42 static GKeyFile *getConfigFile(void) {
43 gchar *filename = get_config_filename();
44
45 GKeyFile *keyfile = g_key_file_new();
46 g_key_file_load_from_file(keyfile, filename,
47 G_KEY_FILE_KEEP_COMMENTS |
48 G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
49
50 g_free(filename);
51 return keyfile;
52 }
53
54
55 /**
56 * Returns the key store password, or the default.
57 */
get_keystore_password(void)58 static const char *get_keystore_password(void) {
59 return (keystore_password != NULL ?
60 keystore_password :
61 /* Silly default value... */
62 "blowinikey");
63 }
64
65
escape_nickname(const char * nick)66 static char *escape_nickname(const char *nick) {
67 char *escaped = g_strdup(nick);
68 char *p = escaped;
69
70 while (*p) {
71 if (*p == '[')
72 *p = '~';
73 else if (*p == ']')
74 *p = '!';
75
76 ++p;
77 }
78
79 return escaped;
80 }
81
82 /**
83 * Gets a value for a nick/channel from addon_fishlim.conf. Unlike
84 * g_key_file_get_string, this function is case insensitive.
85 */
get_nick_value(GKeyFile * keyfile,const char * nick,const char * item)86 static gchar *get_nick_value(GKeyFile *keyfile, const char *nick, const char *item) {
87 gchar **group;
88 gchar **groups = g_key_file_get_groups(keyfile, NULL);
89 gchar *result = NULL;
90
91 for (group = groups; *group != NULL; group++) {
92 if (!irc_nick_cmp(*group, nick)) {
93 result = g_key_file_get_string(keyfile, *group, item, NULL);
94 break;
95 }
96 }
97
98 g_strfreev(groups);
99 return result;
100 }
101
102
103 /**
104 * Extracts a key from the key store file.
105 */
keystore_get_key(const char * nick,enum fish_mode * mode)106 char *keystore_get_key(const char *nick, enum fish_mode *mode) {
107 GKeyFile *keyfile;
108 char *escaped_nick;
109 gchar *value, *key_mode;
110 int encrypted_mode;
111 char *password;
112 char *encrypted;
113 char *decrypted;
114
115 /* Get the key */
116 keyfile = getConfigFile();
117 escaped_nick = escape_nickname(nick);
118 value = get_nick_value(keyfile, escaped_nick, "key");
119 key_mode = get_nick_value(keyfile, escaped_nick, "mode");
120 g_key_file_free(keyfile);
121 g_free(escaped_nick);
122
123 /* Determine cipher mode */
124 *mode = FISH_ECB_MODE;
125 if (key_mode) {
126 if (*key_mode == '1')
127 *mode = FISH_ECB_MODE;
128 else if (*key_mode == '2')
129 *mode = FISH_CBC_MODE;
130 g_free(key_mode);
131 }
132
133 if (!value)
134 return NULL;
135
136 if (strncmp(value, "+OK ", 4) == 0) {
137 /* Key is encrypted */
138 encrypted = (char *) value;
139 encrypted += 4;
140
141 encrypted_mode = FISH_ECB_MODE;
142
143 if (*encrypted == '*') {
144 ++encrypted;
145 encrypted_mode = FISH_CBC_MODE;
146 }
147
148 password = (char *) get_keystore_password();
149 decrypted = fish_decrypt_str((const char *) password, strlen(password), (const char *) encrypted, encrypted_mode);
150 g_free(value);
151 return decrypted;
152 } else {
153 /* Key is stored in plaintext */
154 return value;
155 }
156 }
157
158 /**
159 * Deletes a nick and the associated key in the key store file.
160 */
delete_nick(GKeyFile * keyfile,const char * nick)161 static gboolean delete_nick(GKeyFile *keyfile, const char *nick) {
162 gchar **group;
163 gchar **groups = g_key_file_get_groups(keyfile, NULL);
164 gboolean ok = FALSE;
165
166 for (group = groups; *group != NULL; group++) {
167 if (!irc_nick_cmp(*group, nick)) {
168 ok = g_key_file_remove_group(keyfile, *group, NULL);
169 break;
170 }
171 }
172
173 g_strfreev(groups);
174 return ok;
175 }
176
177 #if !GLIB_CHECK_VERSION(2,40,0)
178 /**
179 * Writes the key store file to disk.
180 */
keyfile_save_to_file(GKeyFile * keyfile,char * filename)181 static gboolean keyfile_save_to_file (GKeyFile *keyfile, char *filename) {
182 gboolean ok;
183
184 /* Serialize */
185 gsize file_length;
186 gchar *file_data = g_key_file_to_data(keyfile, &file_length, NULL);
187 if (!file_data)
188 return FALSE;
189
190 /* Write to file */
191 ok = g_file_set_contents (filename, file_data, file_length, NULL);
192 g_free(file_data);
193 return ok;
194 }
195 #endif
196
197 /**
198 * Writes the key store file to disk.
199 */
save_keystore(GKeyFile * keyfile)200 static gboolean save_keystore(GKeyFile *keyfile) {
201 char *filename;
202 gboolean ok;
203
204 filename = get_config_filename();
205 #if !GLIB_CHECK_VERSION(2,40,0)
206 ok = keyfile_save_to_file (keyfile, filename);
207 #else
208 G_GNUC_BEGIN_IGNORE_DEPRECATIONS /* Hide false positive */
209 ok = g_key_file_save_to_file (keyfile, filename, NULL);
210 G_GNUC_END_IGNORE_DEPRECATIONS
211 #endif
212 g_free (filename);
213
214 return ok;
215 }
216
217 /**
218 * Sets a key in the key store file.
219 */
keystore_store_key(const char * nick,const char * key,enum fish_mode mode)220 gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode) {
221 const char *password;
222 char *encrypted;
223 char *wrapped;
224 gboolean ok = FALSE;
225 GKeyFile *keyfile = getConfigFile();
226 char *escaped_nick = escape_nickname(nick);
227
228 /* Remove old key */
229 delete_nick(keyfile, escaped_nick);
230
231 /* Add new key */
232 password = get_keystore_password();
233 if (password) {
234 /* Encrypt the password */
235 encrypted = fish_encrypt(password, strlen(password), key, strlen(key), FISH_CBC_MODE);
236 if (!encrypted) goto end;
237
238 /* Prepend "+OK " */
239 wrapped = g_strconcat("+OK *", encrypted, NULL);
240 g_free(encrypted);
241
242 /* Store encrypted in file */
243 g_key_file_set_string(keyfile, escaped_nick, "key", wrapped);
244 g_free(wrapped);
245 } else {
246 /* Store unencrypted in file */
247 g_key_file_set_string(keyfile, escaped_nick, "key", key);
248 }
249
250 /* Store cipher mode */
251 g_key_file_set_integer(keyfile, escaped_nick, "mode", mode);
252
253 /* Save key store file */
254 ok = save_keystore(keyfile);
255
256 end:
257 g_key_file_free(keyfile);
258 g_free(escaped_nick);
259 return ok;
260 }
261
262 /**
263 * Deletes a nick from the key store.
264 */
keystore_delete_nick(const char * nick)265 gboolean keystore_delete_nick(const char *nick) {
266 GKeyFile *keyfile = getConfigFile();
267 char *escaped_nick = escape_nickname(nick);
268
269 /* Delete entry */
270 gboolean ok = delete_nick(keyfile, escaped_nick);
271
272 /* Save */
273 if (ok) save_keystore(keyfile);
274
275 g_key_file_free(keyfile);
276 g_free(escaped_nick);
277 return ok;
278 }
279