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