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