1 /*
2 ctrlproxy: A modular IRC proxy
3 (c) 2003-2007 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 3 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 <sys/stat.h>
23 #include <glib/gstdio.h>
24 #include <fcntl.h>
25 #include <errno.h>
26 #include <glib.h>
27 #include "keyfile.h"
28 #include "irc.h"
29
30 #define NICKSERV_FILE_HEADER "# This file contains passwords for NickServ\n" \
31 "# It should contain one entry per line, each entry consisting of: \n" \
32 "# a nick name, password and network name, separated by tabs.\n" \
33 "#\n"
34
nickserv_find_nick(struct irc_network * n,const char * nick)35 const char *nickserv_find_nick(struct irc_network *n, const char *nick)
36 {
37 GList *gl;
38 for (gl = n->global->nickserv_nicks; gl; gl = gl->next) {
39 struct keyfile_entry *e = gl->data;
40
41 if (g_strcasecmp(e->nick, nick))
42 continue;
43
44 if (!e->network) return e->pass;
45 if (!g_strcasecmp(e->network, n->name)) return e->pass;
46 }
47
48 return NULL;
49 }
50
nickserv_nick(struct irc_network * n)51 const char *nickserv_nick(struct irc_network *n)
52 {
53 return "NickServ";
54 }
55
nickserv_identify_me(struct irc_network * network,char * nick)56 void nickserv_identify_me(struct irc_network *network, char *nick)
57 {
58 const char *pass;
59
60 /* Don't try to identify if we're already identified */
61 /* FIXME: Apparently, +e indicates being registered on Freenode,
62 * +R is only used on OFTC */
63 if (network->external_state->me.modes['R'])
64 return;
65
66 pass = nickserv_find_nick(network, nick);
67
68 if (pass) {
69 const char *nickserv_n = nickserv_nick(network);
70 char *raw;
71 raw = g_strdup_printf("IDENTIFY %s", pass);
72 network_log(LOG_INFO, network, "Sending NickServ password for %s", nickserv_n);
73 network_send_args(network, "PRIVMSG", nickserv_n, raw, NULL);
74 g_free(raw);
75 } else {
76 network_log(LOG_TRACE, network, "No NickServ password known for `%s'", nick);
77 }
78 }
79
cache_nickserv_pass(struct irc_network * n,const char * newpass)80 static void cache_nickserv_pass(struct irc_network *n, const char *newpass)
81 {
82 struct keyfile_entry *e = NULL;
83 GList *gl;
84
85 if (!n->global->config->learn_nickserv)
86 return;
87
88 for (gl = n->global->nickserv_nicks; gl; gl = gl->next) {
89 e = gl->data;
90
91 if (e->network && !g_strcasecmp(e->network, n->name) &&
92 !g_strcasecmp(e->nick, n->external_state->me.nick)) {
93 break;
94 }
95
96 if (!e->network && !g_strcasecmp(e->nick, n->external_state->me.nick) &&
97 !g_strcasecmp(e->pass, newpass)) {
98 break;
99 }
100 }
101
102 if (gl == NULL) {
103 e = g_new0(struct keyfile_entry, 1);
104 e->nick = g_strdup(n->external_state->me.nick);
105 e->network = g_strdup(n->name);
106 n->global->nickserv_nicks = g_list_prepend(n->global->nickserv_nicks, e);
107 }
108
109 if (e->pass == NULL || strcmp(e->pass, newpass) != 0) {
110 e->pass = g_strdup(newpass);
111 network_log(LOG_INFO, n, "Caching NickServ password for nick %s", e->nick);
112 }
113 }
114
log_data(struct irc_network * n,const struct irc_line * l,enum data_direction dir,void * userdata)115 static gboolean log_data(struct irc_network *n, const struct irc_line *l, enum data_direction dir, void *userdata)
116 {
117 static char *nickattempt = NULL;
118
119 /* User has changed his/her nick. Check whether this nick needs to be identified */
120 if (dir == FROM_SERVER && !g_strcasecmp(l->args[0], "NICK") &&
121 nickattempt && !g_strcasecmp(nickattempt, l->args[1])) {
122 nickserv_identify_me(n, l->args[1]);
123 }
124
125 /* Keep track of the last nick that the user tried to take */
126 if (dir == TO_SERVER && !g_strcasecmp(l->args[0], "NICK")) {
127 if (nickattempt) g_free(nickattempt);
128 nickattempt = g_strdup(l->args[1]);
129 }
130
131 if (dir == TO_SERVER &&
132 (!g_strcasecmp(l->args[0], "PRIVMSG") || !g_strcasecmp(l->args[0], "NOTICE")) &&
133 (!g_strcasecmp(l->args[1], nickserv_nick(n)) && !g_strncasecmp(l->args[2], "IDENTIFY ", strlen("IDENTIFY ")))) {
134 cache_nickserv_pass(n, l->args[2] + strlen("IDENTIFY "));
135 }
136
137 if (dir == TO_SERVER &&
138 !g_strcasecmp(l->args[0], "NS") &&
139 !g_strcasecmp(l->args[1], "IDENTIFY")) {
140 cache_nickserv_pass(n, l->args[2]);
141 }
142
143 /* If we receive a nick-already-in-use message, ghost the current user */
144 if (dir == FROM_SERVER && atol(l->args[0]) == ERR_NICKNAMEINUSE) {
145 const char *pass = nickserv_find_nick(n, nickattempt);
146 if (nickattempt && pass) {
147 const char *nickserv_n = nickserv_nick(n);
148 char *raw;
149
150 network_log(LOG_INFO, n, "Ghosting current user using '%s'", nickattempt);
151
152 raw = g_strdup_printf("GHOST %s %s", nickattempt, pass);
153 network_send_args(n, "PRIVMSG", nickserv_n, raw, NULL);
154 g_free(raw);
155 network_send_args(n, "NICK", nickattempt, NULL);
156 }
157 }
158
159 return TRUE;
160 }
161
nickserv_save(struct global * global,const char * dir)162 gboolean nickserv_save(struct global *global, const char *dir)
163 {
164 char *filename = g_build_filename(dir, "nickserv", NULL);
165 gboolean ret;
166
167 if (global->nickserv_nicks == NULL) {
168 if (g_unlink(filename) == 0)
169 ret = TRUE;
170 else if (errno == ENOENT)
171 ret = TRUE;
172 else
173 ret = FALSE;
174 } else
175 ret = keyfile_write_file(global->nickserv_nicks, NICKSERV_FILE_HEADER, filename);
176
177 g_free(filename);
178
179 return ret;
180 }
181
nickserv_load(struct global * global)182 gboolean nickserv_load(struct global *global)
183 {
184 gboolean ret;
185 char *filename;
186
187 filename = g_build_filename(global->config->config_dir, "nickserv",
188 NULL);
189 ret = keyfile_read_file(filename, '#', &global->nickserv_nicks);
190 g_free(filename);
191
192 return TRUE;
193 }
194
init_nickserv(void)195 void init_nickserv(void)
196 {
197 add_server_filter("nickserv", log_data, NULL, 1);
198 }
199