Start working on refactoring the network_info code.
[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 "irc.h"
27
28
29 /**
30  * Nickname/password combination for a particular network or globally.
31  */
32 struct nickserv_entry {
33         const char *network;
34         const char *nick;
35         const char *pass;
36 };
37
38 const char *nickserv_find_nick(struct network *n, const char *nick)
39 {
40         GList *gl;
41         for (gl = n->global->nickserv_nicks; gl; gl = gl->next) {
42                 struct nickserv_entry *e = gl->data;
43
44                 if (g_strcasecmp(e->nick, nick)) 
45                         continue;
46
47                 if (!e->network) return e->pass;
48                 if (!g_strcasecmp(e->network, n->name)) return e->pass;
49         }
50         
51         return NULL;
52 }
53
54 const char *nickserv_nick(struct network *n)
55 {
56         return "NickServ";
57 }
58
59 void nickserv_identify_me(struct network *network, char *nick)
60 {
61         const char *pass;
62
63         /* Don't try to identify if we're already identified */
64         /* FIXME: Apparently, +e indicates being registered on Freenode,
65          * +R is only used on OFTC */
66         if (network->state->me.modes['R']) 
67                 return;
68         
69         pass = nickserv_find_nick(network, nick);
70         
71         if (pass) {
72                 const char *nickserv_n = nickserv_nick(network);
73                 char *raw;
74                 raw = g_strdup_printf("IDENTIFY %s", pass);
75                 log_network(LOG_INFO, network, "Sending password for %s", nickserv_n);
76                 network_send_args(network, "PRIVMSG", nickserv_n, raw, NULL);
77                 g_free(raw);
78         } else {
79                 log_network(LOG_INFO, network, "No password known for `%s'", nick);
80         }
81 }
82
83 static gboolean log_data(struct network *n, const struct line *l, enum data_direction dir, void *userdata) 
84 {
85         static char *nickattempt = NULL;
86
87         /* User has changed his/her nick. Check whether this nick needs to be identified */
88         if(dir == FROM_SERVER && !g_strcasecmp(l->args[0], "NICK") &&
89            nickattempt && !g_strcasecmp(nickattempt, l->args[1])) {
90                 nickserv_identify_me(n, l->args[1]);
91         }
92
93         /* Keep track of the last nick that the user tried to take */
94         if(dir == TO_SERVER && !g_strcasecmp(l->args[0], "NICK")) {
95                 if(nickattempt) g_free(nickattempt);
96                 nickattempt = g_strdup(l->args[1]);
97         }
98
99         if (dir == TO_SERVER && 
100                 (!g_strcasecmp(l->args[0], "PRIVMSG") || !g_strcasecmp(l->args[0], "NOTICE")) &&
101                 (!g_strcasecmp(l->args[1], nickserv_nick(n)) && !g_strncasecmp(l->args[2], "IDENTIFY ", strlen("IDENTIFY ")))) {
102                         struct nickserv_entry *e = NULL;
103                         GList *gl;
104                         char *newpass = g_strdup(l->args[2] + strlen("IDENTIFY "));
105                 
106                         for (gl = n->global->nickserv_nicks; gl; gl = gl->next) {
107                                 e = gl->data;
108
109                                 if (e->network && !g_strcasecmp(e->network, n->name) && 
110                                         !g_strcasecmp(e->nick, n->state->me.nick)) {
111                                         break;          
112                                 }
113
114                                 if (!e->network && !g_strcasecmp(e->nick, n->state->me.nick) &&
115                                         !g_strcasecmp(e->pass, newpass)) {
116                                         break;
117                                 }
118                         }
119
120                         if (!gl) {
121                                 e = g_new0(struct nickserv_entry, 1);
122                                 e->nick = g_strdup(n->state->me.nick);
123                                 e->network = g_strdup(n->name);
124                                 n->global->nickserv_nicks = g_list_prepend(n->global->nickserv_nicks, e);
125                         }
126
127                         if (e->pass == NULL || 
128                                 strcmp(e->pass, newpass) != 0) {
129                                 e->pass = g_strdup(newpass);
130                                 log_network(LOG_INFO, n, "Caching password for nick %s", e->nick);
131                         } 
132
133                         g_free(newpass);
134         }
135
136         /* If we receive a nick-already-in-use message, ghost the current user */
137         if(dir == FROM_SERVER && atol(l->args[0]) == ERR_NICKNAMEINUSE) {
138                 const char *pass = nickserv_find_nick(n, nickattempt);
139                 if(nickattempt && pass) {
140                         const char *nickserv_n = nickserv_nick(n);
141                         char *raw;
142                         
143                         log_network(LOG_INFO, n, "Ghosting current user using '%s'", nickattempt);
144
145                         raw = g_strdup_printf("GHOST %s %s", nickattempt, pass);
146                         network_send_args(n, "PRIVMSG", nickserv_n, raw, NULL);
147                         g_free(raw);
148                         network_send_args(n, "NICK", nickattempt, NULL);
149                 }
150         }
151
152         return TRUE;
153 }
154
155
156 gboolean nickserv_save(struct global *global, const char *dir)
157 {
158     char *filename = g_build_filename(dir, "nickserv", NULL);
159     GList *gl;
160         int fd;
161
162         fd = open(filename, O_WRONLY | O_CREAT, 0600);
163
164     if (fd == -1) {
165                 log_global(LOG_WARNING, "Unable to write nickserv file `%s': %s", filename, strerror(errno));
166         g_free(filename);
167         return FALSE;
168     }
169
170         for (gl = global->nickserv_nicks; gl; gl = gl->next) {
171                 struct nickserv_entry *n = gl->data;
172         char *line;
173         
174         line = g_strdup_printf("%s\t%s\t%s\n", n->nick, n->pass, n->network?n->network:"*");
175                 if (write(fd, line, strlen(line)) < 0) {
176                         log_global(LOG_WARNING, "error writing line `%s': %s", line, strerror(errno));
177                 }
178
179         g_free(line);
180         }
181     
182     close(fd);
183     g_free(filename);
184
185         return TRUE;
186 }
187
188 gboolean nickserv_load(struct global *global)
189 {
190     char *filename = g_build_filename(global->config->config_dir, "nickserv", NULL);
191     GIOChannel *gio;
192     char *ret;
193     gsize nr, term;
194
195     gio = g_io_channel_new_file(filename, "r", NULL);
196
197     if (!gio) {
198         g_free(filename);
199         return FALSE;
200     }
201
202     while (G_IO_STATUS_NORMAL == g_io_channel_read_line(gio, &ret, &nr, &term, NULL))
203     {
204         char **parts; 
205                 struct nickserv_entry *e;
206
207                 ret[term] = '\0';
208
209         parts = g_strsplit(ret, "\t", 3);
210         g_free(ret);
211
212                 if (!parts[0] || !parts[1]) {
213                         g_strfreev(parts);
214                         continue;
215                 }
216                         
217                 e = g_new0(struct nickserv_entry, 1);
218                 e->nick = parts[0];
219                 e->pass = parts[1];
220                 if (!parts[2] || !strcmp(parts[2], "*")) {
221                         e->network = NULL;
222                         g_free(parts[2]);
223                 } else {
224                         e->network = parts[2];
225                 }
226         
227                 global->nickserv_nicks = g_list_append(global->nickserv_nicks, e);   
228         g_free(parts);
229     }
230
231         g_free(filename);
232
233         g_io_channel_shutdown(gio, TRUE, NULL);
234         g_io_channel_unref(gio);
235
236         return TRUE;
237 }
238
239 void init_nickserv(void)
240 {
241         add_server_filter("nickserv", log_data, NULL, 1);
242 }