]> git.pond.sub.org Git - empserver/blob - src/lib/player/login.c
Clean up how quit and server shutdown trigger connection close
[empserver] / src / lib / player / login.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2011, Dave Pare, Jeff Bailey, Thomas Ruschak,
4  *                Ken Stevens, Steve McClure, Markus Armbruster
5  *
6  *  Empire is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  *  ---
20  *
21  *  See files README, COPYING and CREDITS in the root of the source
22  *  tree for related information and legal notices.  It is expected
23  *  that future projects/authors will amend these files as needed.
24  *
25  *  ---
26  *
27  *  login.c: Allow the player to login
28  *
29  *  Known contributors to this file:
30  *     Dave Pare, 1994
31  *     Steve McClure, 2000
32  *     Markus Armbruster, 2004-2011
33  *     Ron Koenderink, 2005-2009
34  */
35
36 #include <config.h>
37
38 #include "com.h"
39 #include "empio.h"
40 #include "empthread.h"
41 #include "file.h"
42 #include "journal.h"
43 #include "match.h"
44 #include "misc.h"
45 #include "nat.h"
46 #include "nsc.h"
47 #include "optlist.h"
48 #include "player.h"
49 #include "proto.h"
50 #include "prototypes.h"
51
52 static int client_cmd(void);
53 static int coun_cmd(void);
54 static int kill_cmd(void);
55 static int options_cmd(void);
56 static int pass_cmd(void);
57 static int play_cmd(void);
58 static int quit_cmd(void);
59 static int sanc_cmd(void);
60 static int user_cmd(void);
61
62 static struct cmndstr login_coms[] = {
63     {"client client-id...", 0, client_cmd, 0, 0},
64     {"coun country", 0, coun_cmd, 0, 0},
65     {"kill", 0, kill_cmd, 0, 0},
66     {"options option=value...", 0, options_cmd, 0, 0},
67     {"pass password", 0, pass_cmd, 0, 0},
68     {"play [user [country [password]]]", 0, play_cmd, 0, 0},
69     {"quit", 0, quit_cmd, 0, 0},
70     {"sanc", 0, sanc_cmd, 0, 0},
71     {"user name", 0, user_cmd, 0, 0},
72     {NULL, 0, NULL, 0, 0}
73 };
74
75 /*ARGSUSED*/
76 void
77 player_login(void *ud)
78 {
79     char buf[128];
80     char space[128];
81     int ac;
82     int cmd;
83     int res;
84
85     player->proc = empth_self();
86
87     pr_id(player, C_INIT, "Empire server ready\n");
88
89     for (;;) {
90         io_output(player->iop, 1);
91         if (io_gets(player->iop, buf, sizeof(buf)) < 0) {
92             res = io_input(player->iop, 1);
93             if (res <= 0) {
94                 if (res == 0 && !io_eof(player->iop))
95                     pr_id(player, C_DATA, "idle connection terminated\n");
96                 break;
97             }
98             continue;
99         }
100         journal_input(buf);
101         ac = parse(buf, space, player->argp, NULL, NULL, NULL);
102         if (ac <= 0) {
103             pr_id(player, C_BADCMD, "Can't parse command\n");
104             continue;
105         }
106         cmd = comtch(player->argp[0], login_coms, 0);
107         if (cmd < 0) {
108             pr_id(player, C_BADCMD, "Command %s not found\n", player->argp[0]);
109             continue;
110         }
111         switch (login_coms[cmd].c_addr()) {
112         case RET_OK:
113             break;
114         case RET_FAIL:
115             break;
116         case RET_SYN:
117             pr_id(player, C_BADCMD, "Usage %s\n", login_coms[cmd].c_form);
118             break;
119         default:
120             break;
121         }
122     }
123     player->state = PS_SHUTDOWN;
124     pr_id(player, C_EXIT, "so long...\n");
125     while (io_output(player->iop, 1) > 0) ;
126     player_delete(player);
127     empth_exit();
128     /*NOTREACHED*/
129 }
130
131 static int
132 client_cmd(void)
133 {
134     int i, sz;
135     char *p, *end;
136
137     if (!player->argp[1])
138         return RET_SYN;
139
140     p = player->client;
141     end = player->client + sizeof(player->client) - 1;
142     for (i = 1; player->argp[i]; ++i) {
143         if (i > 1)
144             *p++ = ' ';
145         sz = strlen(player->argp[i]);
146         sz = MIN(sz, end - p);
147         memcpy(p, player->argp[i], sz);
148         p += sz;
149     }
150     *p = 0;
151     pr_id(player, C_CMDOK, "talking to %s\n", player->client);
152     return RET_OK;
153 }
154
155 static int
156 user_cmd(void)
157 {
158     if (!player->argp[1])
159         return RET_SYN;
160     strncpy(player->userid, player->argp[1], sizeof(player->userid) - 1);
161     player->userid[sizeof(player->userid) - 1] = '\0';
162     pr_id(player, C_CMDOK, "hello %s\n", player->userid);
163     return RET_OK;
164 }
165
166 static int
167 sanc_cmd(void)
168 {
169     struct nstr_item ni;
170     struct natstr nat;
171     int first = 1;
172
173     if (!opt_BLITZ) {
174         pr_id(player, C_BADCMD, "Command %s not found\n", player->argp[0]);
175         return RET_FAIL;
176     }
177
178     snxtitem_all(&ni, EF_NATION);
179     while (nxtitem(&ni, &nat)) {
180         if (nat.nat_stat != STAT_SANCT)
181             continue;
182         if (first) {
183             pr_id(player, C_DATA,
184                   "The following countries are still in sanctuary:\n");
185             first = 0;
186         }
187         pr_id(player, C_DATA, "%s\n", nat.nat_cnam);
188     }
189     if (first)
190         pr_id(player, C_CMDOK, "There are no countries in sanctuary\n");
191     else
192         pr_id(player, C_CMDOK, "\n");
193     return RET_OK;
194 }
195
196 static int
197 coun_cmd(void)
198 {
199     natid cnum;
200
201     if (!player->argp[1])
202         return RET_SYN;
203     if (natbyname(player->argp[1], &cnum) < 0) {
204         pr_id(player, C_CMDERR, "country %s does not exist\n", player->argp[1]);
205         return RET_FAIL;
206     }
207     player->cnum = cnum;
208     player->authenticated = 0;
209     pr_id(player, C_CMDOK, "country name %s\n", player->argp[1]);
210     return 0;
211 }
212
213 static int
214 pass_cmd(void)
215 {
216     if (!player->argp[1])
217         return RET_SYN;
218     if (player->cnum == NATID_BAD) {
219         pr_id(player, C_CMDERR, "need country first\n");
220         return RET_FAIL;
221     }
222     if (!natpass(player->cnum, player->argp[1])) {
223         pr_id(player, C_CMDERR, "password bad, logging entry\n");
224         logerror("%s tried country #%d with %s",
225                  praddr(player), player->cnum, player->argp[1]);
226         return RET_FAIL;
227     }
228     player->authenticated = 1;
229     pr_id(player, C_CMDOK, "password ok\n");
230     logerror("%s using country #%d", praddr(player), player->cnum);
231     return RET_OK;
232 }
233
234 static int
235 options_cmd(void)
236 {
237     /*
238      * The option mechanism allows arbitrary string values, but so far
239      * all options are flags in struct player.  Should be easy to
240      * generalize if needed.
241      */
242     struct logoptstr {
243         char *name;
244         int val;
245     };
246     static struct logoptstr login_opts[] = {
247         { "utf-8", PF_UTF8 },
248         { NULL, 0 }
249     };
250     char **ap;
251     char *p;
252     int opt;
253     unsigned i;
254
255     if (!player->argp[1]) {
256         for (i = 0; login_opts[i].name; ++i)
257             pr_id(player, C_DATA, "%s=%d\n",
258                   login_opts[i].name,
259                   (player->flags & login_opts[i].val) != 0);
260         pr_id(player, C_CMDOK, "\n");
261         return RET_OK;
262     }
263
264     for (ap = player->argp+1; *ap; ++ap) {
265         p = strchr(*ap, '=');
266         if (p)
267             *p++ = 0;
268         opt = stmtch(*ap, login_opts,
269                      offsetof(struct logoptstr, name),
270                      sizeof(struct logoptstr));
271         if (opt < 0) {
272             pr_id(player, C_BADCMD, "Option %s not found\n", *ap);
273             return RET_FAIL;
274         }
275         if (!p || atoi(p))
276             player->flags |= login_opts[opt].val;
277         else
278             player->flags &= ~login_opts[opt].val;
279     }
280
281     pr_id(player, C_CMDOK, "Accepted\n");
282
283     return RET_OK;
284 }
285
286 static int
287 may_play(void)
288 {
289     struct natstr *np;
290
291     if (player->cnum == NATID_BAD || !player->authenticated) {
292         pr_id(player, C_CMDERR, "need country and password\n");
293         return 0;
294     }
295     /* TODO strstr() cheesy, compare IP against IP/BITS ... */
296     np = getnatp(player->cnum);
297     if (np->nat_stat == STAT_GOD && *privip
298         && !strstr(privip, player->hostaddr)) {
299         logerror("Deity login from untrusted host attempted by %s",
300                  praddr(player));
301         logerror("To allow this, add %s to econfig key privip",
302                  player->hostaddr);
303         pr_id(player, C_EXIT,
304               "Deity login not allowed from this IP!"
305               "  See log for help on how to allow it.\n");
306         return 0;
307     }
308     return 1;
309 }
310
311 static int
312 play_cmd(void)
313 {
314     struct player *other;
315     natid cnum;
316     struct natstr *natp;
317     char **ap;
318     char buf[128];
319
320     ap = player->argp;
321     if (*++ap) {
322         strncpy(player->userid, *ap, sizeof(player->userid) - 1);
323         player->userid[sizeof(player->userid) - 1] = '\0';
324         player->authenticated = 0;
325     }
326     if (*++ap) {
327         if (natbyname(*ap, &cnum) < 0) {
328             pr_id(player, C_CMDERR, "country %s does not exist\n", *ap);
329             return RET_FAIL;
330         }
331     }
332     if (*++ap) {
333         if (!natpass(cnum, *ap)) {
334             pr_id(player, C_CMDERR, "password bad, logging entry\n");
335             logerror("%s tried country #%d with %s",
336                      praddr(player), cnum, *ap);
337             return RET_FAIL;
338         }
339         player->cnum = cnum;
340         player->authenticated = 1;
341     }
342     if (!may_play())
343         return RET_FAIL;
344     other = getplayer(player->cnum);
345     if (other) {
346         natp = getnatp(player->cnum);
347         if (natp->nat_stat != STAT_VIS) {
348             pr_id(player, C_EXIT, "country in use by %s\n", praddr(other));
349         } else {
350             pr_id(player, C_EXIT, "country in use\n");
351         }
352         return RET_FAIL;
353     }
354     snprintf(buf, sizeof(buf), "Play#%d", player->cnum);
355     empth_set_name(empth_self(), buf);
356     logerror("%s logged in as country #%d", praddr(player), player->cnum);
357     pr_id(player, C_INIT, "%d\n", CLIENTPROTO);
358     player->state = PS_PLAYING;
359     player_main(player);
360     logerror("%s logged out, country #%d", praddr(player), player->cnum);
361     if (CANT_HAPPEN(!io_eof(player->iop)))
362         io_set_eof(player->iop);
363     return RET_OK;
364 }
365
366 static int
367 kill_cmd(void)
368 {
369     struct player *other;
370
371     if (!may_play())
372         return RET_FAIL;
373     other = getplayer(player->cnum);
374     if (!other) {
375         pr_id(player, C_EXIT, "country not in use\n");
376         return RET_FAIL;
377     }
378     logerror("%s killed country #%d", praddr(player), player->cnum);
379     io_shutdown(other->iop, IO_READ | IO_WRITE);
380     pr_id(player, C_EXIT, "closed socket of offending job\n");
381     return RET_OK;
382 }
383
384 static int
385 quit_cmd(void)
386 {
387     io_set_eof(player->iop);
388     return RET_OK;
389 }