daa2f8cc0e625ad475f6f07507e18a56a1971c26
[jelmer/ctrlproxy.git] / src / nickserv.c
1 /* 
2         ctrlproxy: A modular IRC proxy
3         (c) 2003,2006 Jelmer Vernooij <jelmer@nl.linux.org>
4
5         This program is free software; you can redistribute it and/or modify
6         it under the terms of the GNU General Public License as published by
7         the Free Software Foundation; either version 2 of the License, or
8         (at your option) any later version.
9
10         This program is distributed in the hope that it will be useful,
11         but WITHOUT ANY WARRANTY; without even the implied warranty of
12         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13         GNU General Public License for more details.
14
15         You should have received a copy of the GNU General Public License
16         along with this program; if not, write to the Free Software
17         Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 #include "ctrlproxy.h"
21 #include <string.h>
22 #include <unistd.h>
23 #include <sys/stat.h>
24 #include <fcntl.h>
25 #include <errno.h>
26 #include "nickserv.h"
27 #include "irc.h"
28
29
30 const char *nickserv_find_nick(struct network *n, const char *nick)
31 {
32         GList *gl;
33         for (gl = n->global->nickserv_nicks; gl; gl = gl->next) {
34                 struct nickserv_entry *e = gl->data;
35
36                 if (g_strcasecmp(e->nick, nick)) 
37                         continue;
38
39                 if (!e->network) return e->pass;
40                 if (!g_strcasecmp(e->network, n->info.name)) return e->pass;
41         }
42         
43         return NULL;
44 }
45
46 const char *nickserv_nick(struct network *n)
47 {
48         return "NickServ";
49 }
50
51 void nickserv_identify_me(struct network *network, char *nick)
52 {
53         const char *pass;
54
55         /* Don't try to identify if we're already identified */
56         /* FIXME: Apparently, +e indicates being registered on Freenode,
57          * +R is only used on OFTC */
58         if (network->state->me.modes['R']) 
59                 return;
60         
61         pass = nickserv_find_nick(network, nick);
62         
63         if (pass) {
64                 const char *nickserv_n = nickserv_nick(network);
65                 char *raw;
66                 raw = g_strdup_printf("IDENTIFY %s", pass);
67                 log_network(LOG_INFO, network, "Sending password for %s", nickserv_n);
68                 network_send_args(network, "PRIVMSG", nickserv_n, raw, NULL);
69                 g_free(raw);
70         } else {
71                 log_network(LOG_INFO, network, "No password known for `%s'", nick);
72         }
73 }
74
75 static gboolean log_data(struct network *n, const struct line *l, enum data_direction dir, void *userdata) 
76 {
77         static char *nickattempt = NULL;
78
79         /* User has changed his/her nick. Check whether this nick needs to be identified */
80         if(dir == FROM_SERVER && !g_strcasecmp(l->args[0], "NICK") &&
81            nickattempt && !g_strcasecmp(nickattempt, l->args[1])) {
82                 nickserv_identify_me(n, l->args[1]);
83         }
84
85         /* Keep track of the last nick that the user tried to take */
86         if(dir == TO_SERVER && !g_strcasecmp(l->args[0], "NICK")) {
87                 if(nickattempt) g_free(nickattempt);
88                 nickattempt = g_strdup(l->args[1]);
89         }
90
91         if (dir == TO_SERVER && 
92                 (!g_strcasecmp(l->args[0], "PRIVMSG") || !g_strcasecmp(l->args[0], "NOTICE")) &&
93                 (!g_strcasecmp(l->args[1], nickserv_nick(n)) && !g_strncasecmp(l->args[2], "IDENTIFY ", strlen("IDENTIFY ")))) {
94                         struct nickserv_entry *e = NULL;
95                         GList *gl;
96                         char *newpass = g_strdup(l->args[2] + strlen("IDENTIFY "));
97                 
98                         for (gl = n->global->nickserv_nicks; gl; gl = gl->next) {
99                                 e = gl->data;
100
101                                 if (e->network && !g_strcasecmp(e->network, n->info.name) && 
102                                         !g_strcasecmp(e->nick, n->state->me.nick)) {
103                                         break;          
104                                 }
105
106                                 if (!e->network && !g_strcasecmp(e->nick, n->state->me.nick) &&
107                                         !g_strcasecmp(e->pass, newpass)) {
108                                         break;
109                                 }
110                         }
111
112                         if (!gl) {
113                                 e = g_new0(struct nickserv_entry, 1);
114                                 e->nick = g_strdup(n->state->me.nick);
115                                 e->network = g_strdup(n->info.name);
116                                 n->global->nickserv_nicks = g_list_prepend(n->global->nickserv_nicks, e);
117                         }
118
119                         if (e->pass == NULL || 
120                                 strcmp(e->pass, newpass) != 0) {
121                                 e->pass = g_strdup(newpass);
122                                 log_network(LOG_INFO, n, "Caching password for nick %s", e->nick);
123                         } 
124
125                         g_free(newpass);
126         }
127
128         /* If we receive a nick-already-in-use message, ghost the current user */
129         if(dir == FROM_SERVER && atol(l->args[0]) == ERR_NICKNAMEINUSE) {
130                 const char *pass = nickserv_find_nick(n, nickattempt);
131                 if(nickattempt && pass) {
132                         const char *nickserv_n = nickserv_nick(n);
133                         char *raw;
134                         
135                         log_network(LOG_INFO, n, "Ghosting current user using '%s'", nickattempt);
136
137                         raw = g_strdup_printf("GHOST %s %s", nickattempt, pass);
138                         network_send_args(n, "PRIVMSG", nickserv_n, raw, NULL);
139                         g_free(raw);
140                         network_send_args(n, "NICK", nickattempt, NULL);
141                 }
142         }
143
144         return TRUE;
145 }
146
147 gboolean nickserv_write_file(GList *nicks, const char *filename)
148 {
149         GList *gl;
150         int fd;
151
152         fd = open(filename, O_WRONLY | O_CREAT, 0600);
153
154     if (fd == -1) {
155                 log_global(LOG_WARNING, "Unable to write nickserv file `%s': %s", filename, strerror(errno));
156         return FALSE;
157     }
158
159         for (gl = nicks; gl; gl = gl->next) {
160                 struct nickserv_entry *n = gl->data;
161         char *line;
162         
163         line = g_strdup_printf("%s\t%s\t%s\n", n->nick, n->pass, n->network?n->network:"*");
164                 if (write(fd, line, strlen(line)) < 0) {
165                         log_global(LOG_WARNING, "error writing line `%s': %s", line, strerror(errno));
166                         g_free(line);
167                         close(fd);
168                         return FALSE;
169                 }
170
171         g_free(line);
172         }
173     
174     close(fd);
175
176         return TRUE;
177 }
178
179 gboolean nickserv_save(struct global *global, const char *dir)
180 {
181     char *filename = g_build_filename(dir, "nickserv", NULL);
182         gboolean ret;
183
184         ret = nickserv_write_file(global->nickserv_nicks, filename);
185
186     g_free(filename);
187
188         return ret;
189 }
190
191 gboolean nickserv_read_file(const char *filename, GList **nicks)
192 {
193     GIOChannel *gio;
194     char *ret;
195     gsize nr, term;
196
197     gio = g_io_channel_new_file(filename, "r", NULL);
198
199     if (gio == NULL) {
200         return FALSE;
201     }
202
203     while (G_IO_STATUS_NORMAL == g_io_channel_read_line(gio, &ret, &nr, &term, NULL))
204     {
205         char **parts; 
206                 struct nickserv_entry *e;
207
208                 ret[term] = '\0';
209
210         parts = g_strsplit(ret, "\t", 3);
211         g_free(ret);
212
213                 if (!parts[0] || !parts[1]) {
214                         g_strfreev(parts);
215                         continue;
216                 }
217                         
218                 e = g_new0(struct nickserv_entry, 1);
219                 e->nick = parts[0];
220                 e->pass = parts[1];
221                 if (!parts[2] || !strcmp(parts[2], "*")) {
222                         e->network = NULL;
223                         g_free(parts[2]);
224                 } else {
225                         e->network = parts[2];
226                 }
227         
228                 *nicks = g_list_append(*nicks, e);   
229         g_free(parts);
230     }
231
232         g_io_channel_shutdown(gio, TRUE, NULL);
233         g_io_channel_unref(gio);
234
235         return TRUE;
236 }
237
238 gboolean nickserv_load(struct global *global)
239 {
240         gboolean ret;
241     char *filename = g_build_filename(global->config->config_dir, "nickserv", 
242                                                                           NULL);
243         ret = nickserv_read_file(filename, &global->nickserv_nicks);
244         g_free(filename);
245
246         return TRUE;
247 }
248
249 void init_nickserv(void)
250 {
251         add_server_filter("nickserv", log_data, NULL, 1);
252 }