]> arthur.barton.de Git - ngircd-alex.git/blob - src/ngircd/login.c
ngIRCd Release 27
[ngircd-alex.git] / src / ngircd / login.c
1 /*
2  * ngIRCd -- The Next Generation IRC Daemon
3  * Copyright (c)2001-2014 Alexander Barton (alex@barton.de) and Contributors.
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  * Please read the file COPYING, README and AUTHORS for more information.
10  */
11
12 #include "portab.h"
13
14 /**
15  * @file
16  * Functions to deal with client logins
17  */
18
19 #include <assert.h>
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <unistd.h>
24
25 #include "conn.h"
26 #include "class.h"
27 #include "client-cap.h"
28 #include "channel.h"
29 #include "conf.h"
30 #include "parse.h"
31 #include "log.h"
32 #include "messages.h"
33 #include "ngircd.h"
34 #include "irc-channel.h"
35 #include "irc-info.h"
36 #include "irc-mode.h"
37 #include "irc-write.h"
38
39 #include "login.h"
40
41 #ifdef PAM
42
43 #include "io.h"
44 #include "pam.h"
45
46 static void cb_Read_Auth_Result PARAMS((int r_fd, UNUSED short events));
47
48 #endif
49
50 /**
51  * Initiate client login.
52  *
53  * This function is called after the daemon received the required NICK and
54  * USER commands of a new client. If the daemon is compiled with support for
55  * PAM, the authentication sub-processs is forked; otherwise the global server
56  * password is checked.
57  *
58  * @param Client The client logging in.
59  * @returns CONNECTED or DISCONNECTED.
60  */
61 GLOBAL bool
62 Login_User(CLIENT * Client)
63 {
64 #ifdef PAM
65         int pipefd[2], result;
66         pid_t pid;
67 #endif
68         CONN_ID conn;
69
70         assert(Client != NULL);
71         conn = Client_Conn(Client);
72
73 #ifndef STRICT_RFC
74         if (Conf_AuthPing) {
75                 /* Did we receive the "auth PONG" already? */
76                 if (Conn_GetAuthPing(conn)) {
77                         Client_SetType(Client, CLIENT_WAITAUTHPING);
78                         LogDebug("Connection %d: Waiting for AUTH PONG ...", conn);
79                         return CONNECTED;
80                 }
81         }
82 #endif
83
84         /* Still waiting for "CAP END" command? */
85         if (Client_Cap(Client) & CLIENT_CAP_PENDING) {
86                 Client_SetType(Client, CLIENT_WAITCAPEND);
87                 LogDebug("Connection %d: Waiting for CAP END ...", conn);
88                 return CONNECTED;
89         }
90
91 #ifdef PAM
92         if (!Conf_PAM) {
93                 /* Don't do any PAM authentication at all if PAM is not
94                  * enabled, instead emulate the behavior of the daemon
95                  * compiled without PAM support. */
96                 if (strcmp(Conn_Password(conn), Conf_ServerPwd) == 0)
97                         return Login_User_PostAuth(Client);
98                 Client_Reject(Client, "Bad server password", false);
99                 return DISCONNECTED;
100         }
101
102         if (Conf_PAMIsOptional &&
103             strcmp(Conn_Password(conn), "") == 0) {
104                 /* Clients are not required to send a password and to be PAM-
105                  * authenticated at all. If not, they won't become "identified"
106                  * and keep the "~" in their supplied user name.
107                  * Therefore it is sensible to either set Conf_PAMisOptional or
108                  * to enable IDENT lookups -- not both. */
109                 return Login_User_PostAuth(Client);
110         }
111
112         if (Conf_PAM) {
113                 /* Fork child process for PAM authentication; and make sure that the
114                  * process timeout is set higher than the login timeout! */
115                 pid = Proc_Fork(Conn_GetProcStat(conn), pipefd,
116                                 cb_Read_Auth_Result, Conf_PongTimeout + 1);
117                 if (pid > 0) {
118                         LogDebug("Authenticator for connection %d created (PID %d).",
119                                  conn, pid);
120                         return CONNECTED;
121                 } else {
122                         /* Sub process */
123                         Log_Init_Subprocess("Auth");
124                         Conn_CloseAllSockets(NONE);
125                         result = PAM_Authenticate(Client);
126                         if (write(pipefd[1], &result, sizeof(result)) != sizeof(result))
127                                 Log_Subprocess(LOG_ERR,
128                                                "Failed to pipe result to parent!");
129                         Log_Exit_Subprocess("Auth");
130                         exit(0);
131                 }
132         } else return CONNECTED;
133 #else
134         /* Check global server password ... */
135         if (strcmp(Conn_Password(conn), Conf_ServerPwd) != 0) {
136                 /* Bad password! */
137                 Client_Reject(Client, "Bad server password", false);
138                 return DISCONNECTED;
139         }
140         return Login_User_PostAuth(Client);
141 #endif
142 }
143
144 /**
145  * Finish client registration.
146  *
147  * Introduce the new client to the network and send all "hello messages"
148  * to it after authentication has been succeeded.
149  *
150  * @param Client The client logging in.
151  * @return CONNECTED or DISCONNECTED.
152  */
153 GLOBAL bool
154 Login_User_PostAuth(CLIENT *Client)
155 {
156         REQUEST Req;
157         char modes[CLIENT_MODE_LEN + 1];
158
159         assert(Client != NULL);
160
161         if (Class_HandleServerBans(Client) != CONNECTED)
162                 return DISCONNECTED;
163
164         Client_Introduce(NULL, Client, CLIENT_USER);
165
166         if (!IRC_WriteStrClient
167             (Client, RPL_WELCOME_MSG, Client_ID(Client), Client_Mask(Client)))
168                 return false;
169         if (!IRC_WriteStrClient
170             (Client, RPL_YOURHOST_MSG, Client_ID(Client),
171              Client_ID(Client_ThisServer()), PACKAGE_VERSION, HOST_CPU,
172              HOST_VENDOR, HOST_OS))
173                 return false;
174         if (!IRC_WriteStrClient
175             (Client, RPL_CREATED_MSG, Client_ID(Client), NGIRCd_StartStr))
176                 return false;
177         if (!IRC_WriteStrClient
178             (Client, RPL_MYINFO_MSG, Client_ID(Client),
179              Client_ID(Client_ThisServer()), PACKAGE_VERSION, USERMODES,
180              CHANMODES))
181                 return false;
182
183         /* Features supported by this server (005 numeric, ISUPPORT),
184          * see <http://www.irc.org/tech_docs/005.html> for details. */
185         if (!IRC_Send_ISUPPORT(Client))
186                 return DISCONNECTED;
187
188         if (!IRC_Send_LUSERS(Client))
189                 return DISCONNECTED;
190         if (!IRC_Show_MOTD(Client))
191                 return DISCONNECTED;
192
193         /* Set default user modes */
194         if (Conf_DefaultUserModes[0]) {
195                 snprintf(modes, sizeof(modes), "+%s", Conf_DefaultUserModes);
196                 Req.prefix = Client_ID(Client_ThisServer());
197                 Req.command = "MODE";
198                 Req.argc = 2;
199                 Req.argv[0] = Client_ID(Client);
200                 Req.argv[1] = modes;
201                 IRC_MODE(Client, &Req);
202         } else
203                 IRC_SetPenalty(Client, 1);
204
205         /* Autojoin clients to the channels */
206         Login_Autojoin(Client);
207
208         return CONNECTED;
209 }
210
211 /**
212  * Autojoin clients to the channels set by administrator
213  *
214  * Do nothing if autojoin is not set in the configuration or the channel is not
215  * available (any more).
216  **/
217 GLOBAL void
218 Login_Autojoin(CLIENT *Client)
219 {
220         REQUEST Req;
221         const struct Conf_Channel *conf_chan;
222         size_t i, channel_count = array_length(&Conf_Channels, sizeof(*conf_chan));
223
224         conf_chan = array_start(&Conf_Channels);
225         assert(channel_count == 0 || conf_chan != NULL);
226
227         for (i = 0; i < channel_count; i++, conf_chan++) {
228                 if(!conf_chan->autojoin)
229                         continue;
230                 if (!Channel_Search(conf_chan->name))
231                         continue;
232                 Req.prefix = Client_ID(Client_ThisServer());
233                 Req.command = "JOIN";
234                 Req.argc = 1;
235                 Req.argv[0] = (char *)conf_chan->name;
236                 IRC_JOIN(Client, &Req);
237         }
238 }
239
240 #ifdef PAM
241
242 /**
243  * Read result of the authenticator sub-process from pipe
244  *
245  * @param r_fd          File descriptor of the pipe.
246  * @param events        (ignored IO specification)
247  */
248 static void
249 cb_Read_Auth_Result(int r_fd, UNUSED short events)
250 {
251         char user[CLIENT_USER_LEN], *ptr;
252         CONN_ID conn;
253         CLIENT *client;
254         int result;
255         size_t len;
256         PROC_STAT *proc;
257
258         LogDebug("Auth: Got callback on fd %d, events %d", r_fd, events);
259         conn = Conn_GetFromProc(r_fd);
260         if (conn == NONE) {
261                 /* Ops, none found? Probably the connection has already
262                  * been closed!? We'll ignore that ... */
263                 io_close(r_fd);
264                 LogDebug("Auth: Got callback for unknown connection!?");
265                 return;
266         }
267         proc = Conn_GetProcStat(conn);
268         client = Conn_GetClient(conn);
269
270         /* Read result from pipe */
271         len = Proc_Read(proc, &result, sizeof(result));
272         Proc_Close(proc);
273         if (len == 0)
274                 return;
275
276         if (len != sizeof(result)) {
277                 Log(LOG_CRIT, "Auth: Got malformed result!");
278                 Client_Reject(client, "Internal error", false);
279                 return;
280         }
281
282         if (result == true) {
283                 /* Authentication succeeded, now set the correct user name
284                  * supplied by the client (without prepended '~' for example),
285                  * but cut it at the first '@' character: */
286                 strlcpy(user, Client_OrigUser(client), sizeof(user));
287                 ptr = strchr(user, '@');
288                 if (ptr)
289                         *ptr = '\0';
290                 Client_SetUser(client, user, true);
291                 (void)Login_User_PostAuth(client);
292         } else
293                 Client_Reject(client, "Bad password", false);
294 }
295
296 #endif
297
298 /* -eof- */