]> code.citadel.org Git - citadel.git/blob - citadel/user_ops.c
* Allow users to authenticate with either their display name or any valid
[citadel.git] / citadel / user_ops.c
1 /* 
2  * $Id$
3  *
4  * Server functions which perform operations on user objects.
5  *
6  */
7
8 #ifdef DLL_EXPORT
9 #define IN_LIBCIT
10 #endif
11
12 #include "sysdep.h"
13 #include <errno.h>
14 #include <stdlib.h>
15 #include <unistd.h>
16 #include <stdio.h>
17 #include <fcntl.h>
18 #include <signal.h>
19 #include <pwd.h>
20 #include <ctype.h>
21 #include <sys/types.h>
22 #include <sys/wait.h>
23
24 #if TIME_WITH_SYS_TIME
25 # include <sys/time.h>
26 # include <time.h>
27 #else
28 # if HAVE_SYS_TIME_H
29 #  include <sys/time.h>
30 # else
31 #  include <time.h>
32 # endif
33 #endif
34
35 #include <string.h>
36 #include <syslog.h>
37 #include <limits.h>
38 #ifndef ENABLE_CHKPWD
39 #include "auth.h"
40 #endif
41 #include "citadel.h"
42 #include "server.h"
43 #include "database.h"
44 #include "user_ops.h"
45 #include "serv_extensions.h"
46 #include "sysdep_decls.h"
47 #include "support.h"
48 #include "room_ops.h"
49 #include "file_ops.h"
50 #include "control.h"
51 #include "msgbase.h"
52 #include "config.h"
53 #include "tools.h"
54 #include "citserver.h"
55
56
57 /*
58  * makeuserkey() - convert a username into the format used as a database key
59  *                 (it's just the username converted into lower case)
60  */
61 static inline void makeuserkey(char *key, char *username) {
62         int i, len;
63
64         len = strlen(username);
65         for (i=0; i<=len; ++i) {
66                 key[i] = tolower(username[i]);
67         }
68 }
69
70
71 /*
72  * getuser()  -  retrieve named user into supplied buffer.
73  *               returns 0 on success
74  */
75 int getuser(struct ctdluser *usbuf, char name[])
76 {
77
78         char usernamekey[USERNAME_SIZE];
79         char sysuser_name[USERNAME_SIZE];
80         struct cdbdata *cdbus;
81         int using_sysuser = 0;
82
83         memset(usbuf, 0, sizeof(struct ctdluser));
84
85 #ifdef ENABLE_AUTOLOGIN
86         if (CtdlAssociateSystemUser(sysuser_name, name) == 0) {
87                 ++using_sysuser;
88         }
89 #endif
90
91         if (using_sysuser) {
92                 makeuserkey(usernamekey, sysuser_name);
93         }
94         else {
95                 makeuserkey(usernamekey, name);
96         }
97
98         cdbus = cdb_fetch(CDB_USERS, usernamekey, strlen(usernamekey));
99         if (cdbus == NULL) {    /* user not found */
100                 return(1);
101         }
102         memcpy(usbuf, cdbus->ptr,
103                ((cdbus->len > sizeof(struct ctdluser)) ?
104                 sizeof(struct ctdluser) : cdbus->len));
105         cdb_free(cdbus);
106
107         return (0);
108 }
109
110
111 /*
112  * lgetuser()  -  same as getuser() but locks the record
113  */
114 int lgetuser(struct ctdluser *usbuf, char *name)
115 {
116         int retcode;
117
118         retcode = getuser(usbuf, name);
119         if (retcode == 0) {
120                 begin_critical_section(S_USERS);
121         }
122         return (retcode);
123 }
124
125
126 /*
127  * putuser()  -  write user buffer into the correct place on disk
128  */
129 void putuser(struct ctdluser *usbuf)
130 {
131         char usernamekey[USERNAME_SIZE];
132
133         makeuserkey(usernamekey, usbuf->fullname);
134
135         usbuf->version = REV_LEVEL;
136         cdb_store(CDB_USERS,
137                   usernamekey, strlen(usernamekey),
138                   usbuf, sizeof(struct ctdluser));
139
140 }
141
142
143 /*
144  * lputuser()  -  same as putuser() but locks the record
145  */
146 void lputuser(struct ctdluser *usbuf)
147 {
148         putuser(usbuf);
149         end_critical_section(S_USERS);
150 }
151
152 /*
153  * Index-generating function used by Ctdl[Get|Set]Relationship
154  */
155 int GenerateRelationshipIndex(char *IndexBuf,
156                               long RoomID,
157                               long RoomGen,
158                               long UserID)
159 {
160
161         struct {
162                 long iRoomID;
163                 long iRoomGen;
164                 long iUserID;
165         } TheIndex;
166
167         TheIndex.iRoomID = RoomID;
168         TheIndex.iRoomGen = RoomGen;
169         TheIndex.iUserID = UserID;
170
171         memcpy(IndexBuf, &TheIndex, sizeof(TheIndex));
172         return (sizeof(TheIndex));
173 }
174
175
176
177 /*
178  * Back end for CtdlSetRelationship()
179  */
180 void put_visit(struct visit *newvisit)
181 {
182         char IndexBuf[32];
183         int IndexLen;
184
185         /* Generate an index */
186         IndexLen = GenerateRelationshipIndex(IndexBuf,
187                                              newvisit->v_roomnum,
188                                              newvisit->v_roomgen,
189                                              newvisit->v_usernum);
190
191         /* Store the record */
192         cdb_store(CDB_VISIT, IndexBuf, IndexLen,
193                   newvisit, sizeof(struct visit)
194         );
195 }
196
197
198
199
200 /*
201  * Define a relationship between a user and a room
202  */
203 void CtdlSetRelationship(struct visit *newvisit,
204                          struct ctdluser *rel_user,
205                          struct ctdlroom *rel_room)
206 {
207
208
209         /* We don't use these in Citadel because they're implicit by the
210          * index, but they must be present if the database is exported.
211          */
212         newvisit->v_roomnum = rel_room->QRnumber;
213         newvisit->v_roomgen = rel_room->QRgen;
214         newvisit->v_usernum = rel_user->usernum;
215
216         put_visit(newvisit);
217 }
218
219 /*
220  * Locate a relationship between a user and a room
221  */
222 void CtdlGetRelationship(struct visit *vbuf,
223                          struct ctdluser *rel_user,
224                          struct ctdlroom *rel_room)
225 {
226
227         char IndexBuf[32];
228         int IndexLen;
229         struct cdbdata *cdbvisit;
230
231         /* Generate an index */
232         IndexLen = GenerateRelationshipIndex(IndexBuf,
233                                              rel_room->QRnumber,
234                                              rel_room->QRgen,
235                                              rel_user->usernum);
236
237         /* Clear out the buffer */
238         memset(vbuf, 0, sizeof(struct visit));
239
240         cdbvisit = cdb_fetch(CDB_VISIT, IndexBuf, IndexLen);
241         if (cdbvisit != NULL) {
242                 memcpy(vbuf, cdbvisit->ptr,
243                        ((cdbvisit->len > sizeof(struct visit)) ?
244                         sizeof(struct visit) : cdbvisit->len));
245                 cdb_free(cdbvisit);
246         }
247         else {
248                 /* If this is the first time the user has seen this room,
249                  * set the view to be the default for the room.
250                  */
251                 vbuf->v_view = rel_room->QRdefaultview;
252         }
253
254         /* Set v_seen if necessary */
255         if (vbuf->v_seen[0] == 0) {
256                 snprintf(vbuf->v_seen, sizeof vbuf->v_seen, "*:%ld", vbuf->v_lastseen);
257         }
258 }
259
260
261 void MailboxName(char *buf, size_t n, const struct ctdluser *who, const char *prefix)
262 {
263         snprintf(buf, n, "%010ld.%s", who->usernum, prefix);
264 }
265
266
267 /*
268  * Is the user currently logged in an Aide?
269  */
270 int is_aide(void)
271 {
272         if (CC->user.axlevel >= 6)
273                 return (1);
274         else
275                 return (0);
276 }
277
278
279 /*
280  * Is the user currently logged in an Aide *or* the room aide for this room?
281  */
282 int is_room_aide(void)
283 {
284
285         if (!CC->logged_in) {
286                 return (0);
287         }
288
289         if ((CC->user.axlevel >= 6)
290             || (CC->room.QRroomaide == CC->user.usernum)) {
291                 return (1);
292         } else {
293                 return (0);
294         }
295 }
296
297 /*
298  * getuserbynumber()  -  get user by number
299  *                       returns 0 if user was found
300  *
301  * WARNING: don't use this function unless you absolutely have to.  It does
302  *          a sequential search and therefore is computationally expensive.
303  */
304 int getuserbynumber(struct ctdluser *usbuf, long int number)
305 {
306         struct cdbdata *cdbus;
307
308         cdb_rewind(CDB_USERS);
309
310         while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
311                 memset(usbuf, 0, sizeof(struct ctdluser));
312                 memcpy(usbuf, cdbus->ptr,
313                        ((cdbus->len > sizeof(struct ctdluser)) ?
314                         sizeof(struct ctdluser) : cdbus->len));
315                 cdb_free(cdbus);
316                 if (usbuf->usernum == number) {
317                         cdb_close_cursor(CDB_USERS);
318                         return (0);
319                 }
320         }
321         return (-1);
322 }
323
324
325 /*
326  * See if we can translate a system login name (i.e. from /etc/passwd)
327  * to a Citadel screen name.  Returns 0 if one is found.
328  */
329 int CtdlAssociateSystemUser(char *screenname, char *loginname) {
330         struct passwd *p;
331         int a;
332
333         p = (struct passwd *) getpwnam(loginname);
334         if (p != NULL) {
335                 strcpy(screenname, p->pw_gecos);
336                 for (a = 0; a < strlen(screenname); ++a) {
337                         if (screenname[a] == ',') {
338                                 screenname[a] = 0;
339                         }
340                 }
341                 return(0);
342         }
343         return(1);
344 }
345
346
347
348 /*
349  * Back end for cmd_user() and its ilk
350  */
351 int CtdlLoginExistingUser(char *trythisname)
352 {
353         char username[SIZ];
354         int found_user;
355         struct recptypes *valid = NULL;
356
357         if (trythisname == NULL) return login_not_found;
358         safestrncpy(username, trythisname, sizeof username);
359         strproc(username);
360
361         if ((CC->logged_in)) {
362                 return login_already_logged_in;
363         }
364
365         /* First, try to log in as if the supplied name is a display name */
366         found_user = getuser(&CC->user, username);
367
368         /* If that didn't work, try to log in as if the supplied name
369          * is an e-mail address
370          */
371         if (found_user != 0) {
372                 valid = validate_recipients(trythisname);
373                 if (valid != NULL) {
374                         if (valid->num_local == 1) {
375                                 found_user = getuser(&CC->user,
376                                                 valid->recp_local);
377                         }
378                         phree(valid);
379                 }
380         }
381
382         /* Did we find something? */
383         if (found_user == 0) {
384                 if (((CC->nologin)) && (CC->user.axlevel < 6)) {
385                         return login_too_many_users;
386                 } else {
387                         safestrncpy(CC->curr_user, CC->user.fullname,
388                                         sizeof CC->curr_user);
389                         return login_ok;
390                 }
391         }
392         return login_not_found;
393 }
394
395
396
397 /*
398  * USER cmd
399  */
400 void cmd_user(char *cmdbuf)
401 {
402         char username[SIZ];
403         int a;
404
405         extract(username, cmdbuf, 0);
406         striplt(username);
407
408         a = CtdlLoginExistingUser(username);
409         switch (a) {
410         case login_already_logged_in:
411                 cprintf("%d Already logged in.\n", ERROR);
412                 return;
413         case login_too_many_users:
414                 cprintf("%d %s: "
415                         "Too many users are already online "
416                         "(maximum is %d)\n",
417                         ERROR + MAX_SESSIONS_EXCEEDED,
418                         config.c_nodename, config.c_maxsessions);
419                 return;
420         case login_ok:
421                 cprintf("%d Password required for %s\n",
422                         MORE_DATA, CC->curr_user);
423                 return;
424         case login_not_found:
425                 cprintf("%d %s not found.\n", ERROR, username);
426                 return;
427                 cprintf("%d Internal error\n", ERROR);
428         }
429 }
430
431
432
433 /*
434  * session startup code which is common to both cmd_pass() and cmd_newu()
435  */
436 void session_startup(void)
437 {
438         int i;
439
440         syslog(LOG_NOTICE, "session %d: user <%s> logged in",
441                CC->cs_pid, CC->curr_user);
442
443         lgetuser(&CC->user, CC->curr_user);
444         ++(CC->user.timescalled);
445         CC->previous_login = CC->user.lastcall;
446         time(&CC->user.lastcall);
447
448         /* If this user's name is the name of the system administrator
449          * (as specified in setup), automatically assign access level 6.
450          */
451         if (!strcasecmp(CC->user.fullname, config.c_sysadm)) {
452                 CC->user.axlevel = 6;
453         }
454         lputuser(&CC->user);
455
456         /*
457          * Populate CC->cs_inet_email with a default address.  This will be
458          * overwritten with the user's directory address, if one exists, when
459          * the vCard module's login hook runs.
460          */
461         snprintf(CC->cs_inet_email, sizeof CC->cs_inet_email, "%s@%s",
462                 CC->user.fullname, config.c_fqdn);
463         for (i=0; i<strlen(CC->cs_inet_email); ++i) {
464                 if (isspace(CC->cs_inet_email[i])) {
465                         CC->cs_inet_email[i] = '_';
466                 }
467         }
468
469         /* Create any personal rooms required by the system.
470          * (Technically, MAILROOM should be there already, but just in case...)
471          */
472         create_room(MAILROOM, 4, "", 0, 1, 0);
473         create_room(SENTITEMS, 4, "", 0, 1, 0);
474
475         /* Run any startup routines registered by loadable modules */
476         PerformSessionHooks(EVT_LOGIN);
477
478         /* Enter the lobby */
479         usergoto(config.c_baseroom, 0, 0, NULL, NULL);
480 }
481
482
483 void logged_in_response(void)
484 {
485         cprintf("%d %s|%d|%ld|%ld|%u|%ld|%ld\n",
486                 CIT_OK, CC->user.fullname, CC->user.axlevel,
487                 CC->user.timescalled, CC->user.posted,
488                 CC->user.flags, CC->user.usernum,
489                 CC->previous_login);
490 }
491
492
493
494 /* 
495  * misc things to be taken care of when a user is logged out
496  */
497 void logout(struct CitContext *who)
498 {
499         who->logged_in = 0;
500
501         /*
502          * If there is a download in progress, abort it.
503          */
504         if (who->download_fp != NULL) {
505                 fclose(who->download_fp);
506                 who->download_fp = NULL;
507         }
508
509         /*
510          * If there is an upload in progress, abort it.
511          */
512         if (who->upload_fp != NULL) {
513                 abort_upl(who);
514         }
515
516         /*
517          * If we were talking to a network node, we're not anymore...
518          */
519         if (strlen(who->net_node) > 0) {
520                 network_talking_to(who->net_node, NTT_REMOVE);
521         }
522
523         /* Do modular stuff... */
524         PerformSessionHooks(EVT_LOGOUT);
525 }
526
527 #ifdef ENABLE_CHKPWD
528 /*
529  * an alternate version of validpw() which executes `chkpwd' instead of
530  * verifying the password directly
531  */
532 static int validpw(uid_t uid, const char *pass)
533 {
534         pid_t pid;
535         int status, pipev[2];
536         char buf[24];
537
538         if (pipe(pipev)) {
539                 lprintf(1, "pipe failed (%s): denying autologin access for "
540                         "uid %ld\n", strerror(errno), (long)uid);
541                 return 0;
542         }
543         switch (pid = fork()) {
544         case -1:
545                 lprintf(1, "fork failed (%s): denying autologin access for "
546                         "uid %ld\n", strerror(errno), (long)uid);
547                 close(pipev[0]);
548                 close(pipev[1]);
549                 return 0;
550
551         case 0:
552                 close(pipev[1]);
553                 if (dup2(pipev[0], 0) == -1) {
554                         perror("dup2");
555                         exit(1);
556                 }
557                 close(pipev[0]);
558
559                 execl(BBSDIR "/chkpwd", BBSDIR "/chkpwd", NULL);
560                 perror(BBSDIR "/chkpwd");
561                 exit(1);
562         }
563
564         close(pipev[0]);
565         write(pipev[1], buf,
566               snprintf(buf, sizeof buf, "%lu\n", (unsigned long) uid));
567         write(pipev[1], pass, strlen(pass));
568         write(pipev[1], "\n", 1);
569         close(pipev[1]);
570
571         while (waitpid(pid, &status, 0) == -1)
572                 if (errno != EINTR) {
573                         lprintf(1, "waitpid failed (%s): denying autologin "
574                                 "access for uid %ld\n",
575                                 strerror(errno), (long)uid);
576                         return 0;
577                 }
578         if (WIFEXITED(status) && !WEXITSTATUS(status))
579                 return 1;
580
581         return 0;
582 }
583 #endif
584
585 void do_login()
586 {
587         (CC->logged_in) = 1;
588         session_startup();
589 }
590
591
592 int CtdlTryPassword(char *password)
593 {
594         int code;
595
596         if ((CC->logged_in)) {
597                 lprintf(5, "CtdlTryPassword: already logged in\n");
598                 return pass_already_logged_in;
599         }
600         if (!strcmp(CC->curr_user, NLI)) {
601                 lprintf(5, "CtdlTryPassword: no user selected\n");
602                 return pass_no_user;
603         }
604         if (getuser(&CC->user, CC->curr_user)) {
605                 lprintf(5, "CtdlTryPassword: internal error\n");
606                 return pass_internal_error;
607         }
608         if (password == NULL) {
609                 lprintf(5, "CtdlTryPassword: NULL password string supplied\n");
610                 return pass_wrong_password;
611         }
612         code = (-1);
613
614
615 #ifdef ENABLE_AUTOLOGIN
616         /* A uid of BBSUID or -1 indicates that this user exists only in
617          * Citadel, not in the underlying operating system.
618          */
619         if ( (CC->user.uid == BBSUID) || (CC->user.uid == (-1)) ) {
620                 strproc(password);
621                 strproc(CC->user.password);
622                 code = strcasecmp(CC->user.password, password);
623         }
624         /* Any other uid means we have to check the system password database */
625         else {
626                 if (validpw(CC->user.uid, password)) {
627                         code = 0;
628                         lgetuser(&CC->user, CC->curr_user);
629                         safestrncpy(CC->user.password, password,
630                                     sizeof CC->user.password);
631                         lputuser(&CC->user);
632                 }
633         }
634
635 #else /* ENABLE_AUTOLOGIN */
636         strproc(password);
637         strproc(CC->user.password);
638         code = strcasecmp(CC->user.password, password);
639
640 #endif /* ENABLE_AUTOLOGIN */
641
642         if (!code) {
643                 do_login();
644                 return pass_ok;
645         } else {
646                 lprintf(3, "Bad password specified for <%s>\n", CC->curr_user);
647                 return pass_wrong_password;
648         }
649 }
650
651
652 void cmd_pass(char *buf)
653 {
654         char password[SIZ];
655         int a;
656
657         extract(password, buf, 0);
658         a = CtdlTryPassword(password);
659
660         switch (a) {
661         case pass_already_logged_in:
662                 cprintf("%d Already logged in.\n", ERROR);
663                 return;
664         case pass_no_user:
665                 cprintf("%d You must send a name with USER first.\n",
666                         ERROR);
667                 return;
668         case pass_wrong_password:
669                 cprintf("%d Wrong password.\n", ERROR);
670                 return;
671         case pass_ok:
672                 logged_in_response();
673                 return;
674                 cprintf("%d Can't find user record!\n",
675                         ERROR + INTERNAL_ERROR);
676         }
677 }
678
679
680
681 /*
682  * Delete a user record *and* all of its related resources.
683  */
684 int purge_user(char pname[])
685 {
686         char filename[64];
687         struct ctdluser usbuf;
688         char usernamekey[USERNAME_SIZE];
689         struct CitContext *ccptr;
690         int user_is_logged_in = 0;
691
692         makeuserkey(usernamekey, pname);
693
694         if (getuser(&usbuf, pname) != 0) {
695                 lprintf(5, "Cannot purge user <%s> - not found\n", pname);
696                 return (ERROR + NO_SUCH_USER);
697         }
698         /* Don't delete a user who is currently logged in.  Instead, just
699          * set the access level to 0, and let the account get swept up
700          * during the next purge.
701          */
702         user_is_logged_in = 0;
703         begin_critical_section(S_SESSION_TABLE);
704         for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
705                 if (ccptr->user.usernum == usbuf.usernum) {
706                         user_is_logged_in = 1;
707                 }
708         }
709         end_critical_section(S_SESSION_TABLE);
710         if (user_is_logged_in == 1) {
711                 lprintf(5, "User <%s> is logged in; not deleting.\n", pname);
712                 usbuf.axlevel = 0;
713                 putuser(&usbuf);
714                 return (1);
715         }
716         lprintf(5, "Deleting user <%s>\n", pname);
717
718         /* Perform any purge functions registered by server extensions */
719         PerformUserHooks(usbuf.fullname, usbuf.usernum, EVT_PURGEUSER);
720
721         /* delete any existing user/room relationships */
722         cdb_delete(CDB_VISIT, &usbuf.usernum, sizeof(long));
723
724         /* delete the userlog entry */
725         cdb_delete(CDB_USERS, usernamekey, strlen(usernamekey));
726
727         /* remove the user's bio file */
728         snprintf(filename, sizeof filename, "./bio/%ld", usbuf.usernum);
729         unlink(filename);
730
731         /* remove the user's picture */
732         snprintf(filename, sizeof filename, "./userpics/%ld.gif", usbuf.usernum);
733         unlink(filename);
734
735         return (0);
736 }
737
738
739 /*
740  * create_user()  -  back end processing to create a new user
741  *
742  * Set 'newusername' to the desired account name.
743  * Set 'become_user' to nonzero if this is self-service account creation and we want
744  * to actually log in as the user we just created, otherwise set it to 0.
745  */
746 int create_user(char *newusername, int become_user)
747 {
748         struct ctdluser usbuf;
749         struct ctdlroom qrbuf;
750         struct passwd *p = NULL;
751         char username[SIZ];
752         char mailboxname[ROOMNAMELEN];
753         uid_t uid;
754
755         safestrncpy(username, newusername, sizeof username);
756         strproc(username);
757
758 #ifdef ENABLE_AUTOLOGIN
759         p = (struct passwd *) getpwnam(username);
760         if (p != NULL) {
761                 extract_token(username, p->pw_gecos, 0, ',');
762                 uid = p->pw_uid;
763         } else {
764                 uid = (-1);
765         }
766 #else
767         uid = (-1);
768 #endif
769
770         if (!getuser(&usbuf, username)) {
771                 return (ERROR + ALREADY_EXISTS);
772         }
773
774         /* Go ahead and initialize a new user record */
775         memset(&usbuf, 0, sizeof(struct ctdluser));
776         safestrncpy(usbuf.fullname, username, sizeof usbuf.fullname);
777         strcpy(usbuf.password, "");
778         usbuf.uid = uid;
779
780         /* These are the default flags on new accounts */
781         usbuf.flags = US_LASTOLD | US_DISAPPEAR | US_PAGINATOR | US_FLOORS;
782
783         usbuf.timescalled = 0;
784         usbuf.posted = 0;
785         usbuf.axlevel = config.c_initax;
786         usbuf.USscreenwidth = 80;
787         usbuf.USscreenheight = 24;
788         usbuf.lastcall = time(NULL);
789
790         /* fetch a new user number */
791         usbuf.usernum = get_new_user_number();
792
793         /* The very first user created on the system will always be an Aide */
794         if (usbuf.usernum == 1L) {
795                 usbuf.axlevel = 6;
796         }
797
798         /* add user to userlog */
799         putuser(&usbuf);
800
801         /*
802          * Give the user a private mailbox and a configuration room.
803          * Make the latter an invisible system room.
804          */
805         MailboxName(mailboxname, sizeof mailboxname, &usbuf, MAILROOM);
806         create_room(mailboxname, 5, "", 0, 1, 1);
807
808         MailboxName(mailboxname, sizeof mailboxname, &usbuf, USERCONFIGROOM);
809         create_room(mailboxname, 5, "", 0, 1, 1);
810         if (lgetroom(&qrbuf, mailboxname) == 0) {
811                 qrbuf.QRflags2 |= QR2_SYSTEM;
812                 lputroom(&qrbuf);
813         }
814
815         /* Everything below this line can be bypassed if administratively
816            creating a user, instead of doing self-service account creation
817          */
818
819         if (become_user) {
820                 /* Now become the user we just created */
821                 memcpy(&CC->user, &usbuf, sizeof(struct ctdluser));
822                 safestrncpy(CC->curr_user, username, sizeof CC->curr_user);
823                 CC->logged_in = 1;
824         
825                 /* Check to make sure we're still who we think we are */
826                 if (getuser(&CC->user, CC->curr_user)) {
827                         return (ERROR + INTERNAL_ERROR);
828                 }
829         }
830
831         lprintf(3, "New user <%s> created\n", username);
832         return (0);
833 }
834
835
836
837
838 /*
839  * cmd_newu()  -  create a new user account and log in as that user
840  */
841 void cmd_newu(char *cmdbuf)
842 {
843         int a;
844         char username[SIZ];
845
846         if (config.c_disable_newu) {
847                 cprintf("%d Self-service user account creation "
848                         "is disabled on this system.\n", ERROR);
849                 return;
850         }
851
852         if (CC->logged_in) {
853                 cprintf("%d Already logged in.\n", ERROR);
854                 return;
855         }
856         if (CC->nologin) {
857                 cprintf("%d %s: Too many users are already online (maximum is %d)\n",
858                         ERROR + MAX_SESSIONS_EXCEEDED,
859                         config.c_nodename, config.c_maxsessions);
860         }
861         extract(username, cmdbuf, 0);
862         username[25] = 0;
863         strproc(username);
864
865         if (strlen(username) == 0) {
866                 cprintf("%d You must supply a user name.\n", ERROR);
867                 return;
868         }
869
870         if ((!strcasecmp(username, "bbs")) ||
871             (!strcasecmp(username, "new")) ||
872             (!strcasecmp(username, "."))) {
873                 cprintf("%d '%s' is an invalid login name.\n", ERROR, username);
874                 return;
875         }
876
877         a = create_user(username, 1);
878
879         if (a == 0) {
880                 session_startup();
881                 logged_in_response();
882         } else if (a == ERROR + ALREADY_EXISTS) {
883                 cprintf("%d '%s' already exists.\n",
884                         ERROR + ALREADY_EXISTS, username);
885                 return;
886         } else if (a == ERROR + INTERNAL_ERROR) {
887                 cprintf("%d Internal error - user record disappeared?\n",
888                         ERROR + INTERNAL_ERROR);
889                 return;
890         } else {
891                 cprintf("%d unknown error\n", ERROR);
892         }
893 }
894
895
896
897 /*
898  * set password
899  */
900 void cmd_setp(char *new_pw)
901 {
902         if (CtdlAccessCheck(ac_logged_in)) {
903                 return;
904         }
905         if ( (CC->user.uid != BBSUID) && (CC->user.uid != (-1)) ) {
906                 cprintf("%d Not allowed.  Use the 'passwd' command.\n", ERROR);
907                 return;
908         }
909         strproc(new_pw);
910         if (strlen(new_pw) == 0) {
911                 cprintf("%d Password unchanged.\n", CIT_OK);
912                 return;
913         }
914         lgetuser(&CC->user, CC->curr_user);
915         safestrncpy(CC->user.password, new_pw, sizeof(CC->user.password));
916         lputuser(&CC->user);
917         cprintf("%d Password changed.\n", CIT_OK);
918         lprintf(3, "Password changed for user <%s>\n", CC->curr_user);
919         PerformSessionHooks(EVT_SETPASS);
920 }
921
922
923 /*
924  * cmd_creu()  -  administratively create a new user account (do not log in to it)
925  */
926 void cmd_creu(char *cmdbuf)
927 {
928         int a;
929         char username[SIZ];
930
931         if (CtdlAccessCheck(ac_aide)) {
932                 return;
933         }
934
935         extract(username, cmdbuf, 0);
936         username[25] = 0;
937         strproc(username);
938
939         if (strlen(username) == 0) {
940                 cprintf("%d You must supply a user name.\n", ERROR);
941                 return;
942         }
943
944         a = create_user(username, 0);
945
946         if (a == 0) {
947                 cprintf("%d ok\n", CIT_OK);
948                 return;
949         } else if (a == ERROR + ALREADY_EXISTS) {
950                 cprintf("%d '%s' already exists.\n",
951                         ERROR + ALREADY_EXISTS, username);
952                 return;
953         } else {
954                 cprintf("%d An error occured creating the user account.\n", ERROR);
955         }
956 }
957
958
959
960 /*
961  * get user parameters
962  */
963 void cmd_getu(void)
964 {
965
966         if (CtdlAccessCheck(ac_logged_in))
967                 return;
968
969         getuser(&CC->user, CC->curr_user);
970         cprintf("%d %d|%d|%d|\n",
971                 CIT_OK,
972                 CC->user.USscreenwidth,
973                 CC->user.USscreenheight,
974                 (CC->user.flags & US_USER_SET)
975             );
976 }
977
978 /*
979  * set user parameters
980  */
981 void cmd_setu(char *new_parms)
982 {
983         if (CtdlAccessCheck(ac_logged_in))
984                 return;
985
986         if (num_parms(new_parms) < 3) {
987                 cprintf("%d Usage error.\n", ERROR);
988                 return;
989         }
990         lgetuser(&CC->user, CC->curr_user);
991         CC->user.USscreenwidth = extract_int(new_parms, 0);
992         CC->user.USscreenheight = extract_int(new_parms, 1);
993         CC->user.flags = CC->user.flags & (~US_USER_SET);
994         CC->user.flags = CC->user.flags |
995             (extract_int(new_parms, 2) & US_USER_SET);
996
997         lputuser(&CC->user);
998         cprintf("%d Ok\n", CIT_OK);
999 }
1000
1001 /*
1002  * set last read pointer
1003  */
1004 void cmd_slrp(char *new_ptr)
1005 {
1006         long newlr;
1007         struct visit vbuf;
1008         struct visit original_vbuf;
1009
1010         if (CtdlAccessCheck(ac_logged_in)) {
1011                 return;
1012         }
1013
1014         if (!strncasecmp(new_ptr, "highest", 7)) {
1015                 newlr = CC->room.QRhighest;
1016         } else {
1017                 newlr = atol(new_ptr);
1018         }
1019
1020         lgetuser(&CC->user, CC->curr_user);
1021
1022         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
1023         memcpy(&original_vbuf, &vbuf, sizeof(struct visit));
1024         vbuf.v_lastseen = newlr;
1025         snprintf(vbuf.v_seen, sizeof vbuf.v_seen, "*:%ld", newlr);
1026
1027         /* Only rewrite the record if it changed */
1028         if ( (vbuf.v_lastseen != original_vbuf.v_lastseen)
1029            || (strcmp(vbuf.v_seen, original_vbuf.v_seen)) ) {
1030                 CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
1031         }
1032
1033         lputuser(&CC->user);
1034         cprintf("%d %ld\n", CIT_OK, newlr);
1035 }
1036
1037
1038 void cmd_seen(char *argbuf) {
1039         long target_msgnum = 0L;
1040         int target_setting = 0;
1041
1042         if (CtdlAccessCheck(ac_logged_in)) {
1043                 return;
1044         }
1045
1046         if (num_parms(argbuf) != 2) {
1047                 cprintf("%d Invalid parameters\n", ERROR);
1048                 return;
1049         }
1050
1051         target_msgnum = extract_long(argbuf, 0);
1052         target_setting = extract_int(argbuf, 1);
1053
1054         CtdlSetSeen(target_msgnum, target_setting);
1055         cprintf("%d OK\n", CIT_OK);
1056 }
1057
1058
1059 void cmd_gtsn(char *argbuf) {
1060         char buf[SIZ];
1061
1062         if (CtdlAccessCheck(ac_logged_in)) {
1063                 return;
1064         }
1065
1066         CtdlGetSeen(buf);
1067         cprintf("%d %s\n", CIT_OK, buf);
1068 }
1069
1070
1071
1072 /*
1073  * INVT and KICK commands
1074  */
1075 void cmd_invt_kick(char *iuser, int op)
1076                         /* user name */
1077 {                               /* 1 = invite, 0 = kick out */
1078         struct ctdluser USscratch;
1079         char bbb[SIZ];
1080         struct visit vbuf;
1081
1082         /*
1083          * These commands are only allowed by aides, room aides,
1084          * and room namespace owners
1085          */
1086         if (is_room_aide()
1087            || (atol(CC->room.QRname) == CC->user.usernum) ) {
1088                 /* access granted */
1089         } else {
1090                 /* access denied */
1091                 cprintf("%d Higher access or room ownership required.\n",
1092                         ERROR + HIGHER_ACCESS_REQUIRED);
1093                 return;
1094         }
1095
1096         if (!strncasecmp(CC->room.QRname, config.c_baseroom,
1097                          ROOMNAMELEN)) {
1098                 cprintf("%d Can't add/remove users from this room.\n",
1099                         ERROR + NOT_HERE);
1100                 return;
1101         }
1102
1103         if (lgetuser(&USscratch, iuser) != 0) {
1104                 cprintf("%d No such user.\n", ERROR);
1105                 return;
1106         }
1107         CtdlGetRelationship(&vbuf, &USscratch, &CC->room);
1108
1109         if (op == 1) {
1110                 vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT;
1111                 vbuf.v_flags = vbuf.v_flags | V_ACCESS;
1112         }
1113         if (op == 0) {
1114                 vbuf.v_flags = vbuf.v_flags & ~V_ACCESS;
1115                 vbuf.v_flags = vbuf.v_flags | V_FORGET | V_LOCKOUT;
1116         }
1117         CtdlSetRelationship(&vbuf, &USscratch, &CC->room);
1118
1119         lputuser(&USscratch);
1120
1121         /* post a message in Aide> saying what we just did */
1122         snprintf(bbb, sizeof bbb, "%s %s %s> by %s\n",
1123                 iuser,
1124                 ((op == 1) ? "invited to" : "kicked out of"),
1125                 CC->room.QRname,
1126                 CC->user.fullname);
1127         aide_message(bbb);
1128
1129         cprintf("%d %s %s %s.\n",
1130                 CIT_OK, iuser,
1131                 ((op == 1) ? "invited to" : "kicked out of"),
1132                 CC->room.QRname);
1133         return;
1134 }
1135
1136
1137 /*
1138  * Forget (Zap) the current room (API call)
1139  * Returns 0 on success
1140  */
1141 int CtdlForgetThisRoom(void) {
1142         struct visit vbuf;
1143
1144         /* On some systems, Aides are not allowed to forget rooms */
1145         if (is_aide() && (config.c_aide_zap == 0)
1146            && ((CC->room.QRflags & QR_MAILBOX) == 0)  ) {
1147                 return(1);
1148         }
1149
1150         lgetuser(&CC->user, CC->curr_user);
1151         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
1152
1153         vbuf.v_flags = vbuf.v_flags | V_FORGET;
1154         vbuf.v_flags = vbuf.v_flags & ~V_ACCESS;
1155
1156         CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
1157         lputuser(&CC->user);
1158
1159         /* Return to the Lobby, so we don't end up in an undefined room */
1160         usergoto(config.c_baseroom, 0, 0, NULL, NULL);
1161         return(0);
1162
1163 }
1164
1165
1166 /*
1167  * forget (Zap) the current room
1168  */
1169 void cmd_forg(void)
1170 {
1171
1172         if (CtdlAccessCheck(ac_logged_in)) {
1173                 return;
1174         }
1175
1176         if (CtdlForgetThisRoom() == 0) {
1177                 cprintf("%d Ok\n", CIT_OK);
1178         }
1179         else {
1180                 cprintf("%d You may not forget this room.\n", ERROR);
1181         }
1182 }
1183
1184 /*
1185  * Get Next Unregistered User
1186  */
1187 void cmd_gnur(void)
1188 {
1189         struct cdbdata *cdbus;
1190         struct ctdluser usbuf;
1191
1192         if (CtdlAccessCheck(ac_aide)) {
1193                 return;
1194         }
1195
1196         if ((CitControl.MMflags & MM_VALID) == 0) {
1197                 cprintf("%d There are no unvalidated users.\n", CIT_OK);
1198                 return;
1199         }
1200
1201         /* There are unvalidated users.  Traverse the user database,
1202          * and return the first user we find that needs validation.
1203          */
1204         cdb_rewind(CDB_USERS);
1205         while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
1206                 memset(&usbuf, 0, sizeof(struct ctdluser));
1207                 memcpy(&usbuf, cdbus->ptr,
1208                        ((cdbus->len > sizeof(struct ctdluser)) ?
1209                         sizeof(struct ctdluser) : cdbus->len));
1210                 cdb_free(cdbus);
1211                 if ((usbuf.flags & US_NEEDVALID)
1212                     && (usbuf.axlevel > 0)) {
1213                         cprintf("%d %s\n", MORE_DATA, usbuf.fullname);
1214                         cdb_close_cursor(CDB_USERS);
1215                         return;
1216                 }
1217         }
1218
1219         /* If we get to this point, there are no more unvalidated users.
1220          * Therefore we clear the "users need validation" flag.
1221          */
1222
1223         begin_critical_section(S_CONTROL);
1224         get_control();
1225         CitControl.MMflags = CitControl.MMflags & (~MM_VALID);
1226         put_control();
1227         end_critical_section(S_CONTROL);
1228         cprintf("%d *** End of registration.\n", CIT_OK);
1229
1230
1231 }
1232
1233
1234 /*
1235  * validate a user
1236  */
1237 void cmd_vali(char *v_args)
1238 {
1239         char user[SIZ];
1240         int newax;
1241         struct ctdluser userbuf;
1242
1243         extract(user, v_args, 0);
1244         newax = extract_int(v_args, 1);
1245
1246         if (CtdlAccessCheck(ac_aide)) {
1247                 return;
1248         }
1249
1250         if (lgetuser(&userbuf, user) != 0) {
1251                 cprintf("%d '%s' not found.\n", ERROR + NO_SUCH_USER, user);
1252                 return;
1253         }
1254
1255         userbuf.axlevel = newax;
1256         userbuf.flags = (userbuf.flags & ~US_NEEDVALID);
1257
1258         lputuser(&userbuf);
1259
1260         /* If the access level was set to zero, delete the user */
1261         if (newax == 0) {
1262                 if (purge_user(user) == 0) {
1263                         cprintf("%d %s Deleted.\n", CIT_OK, userbuf.fullname);
1264                         return;
1265                 }
1266         }
1267         cprintf("%d User '%s' validated.\n", CIT_OK, userbuf.fullname);
1268 }
1269
1270
1271
1272 /* 
1273  *  Traverse the user file...
1274  */
1275 void ForEachUser(void (*CallBack) (struct ctdluser * EachUser, void *out_data),
1276                  void *in_data)
1277 {
1278         struct ctdluser usbuf;
1279         struct cdbdata *cdbus;
1280
1281         cdb_rewind(CDB_USERS);
1282
1283         while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
1284                 memset(&usbuf, 0, sizeof(struct ctdluser));
1285                 memcpy(&usbuf, cdbus->ptr,
1286                        ((cdbus->len > sizeof(struct ctdluser)) ?
1287                         sizeof(struct ctdluser) : cdbus->len));
1288                 cdb_free(cdbus);
1289                 (*CallBack) (&usbuf, in_data);
1290         }
1291 }
1292
1293
1294 /*
1295  * List one user (this works with cmd_list)
1296  */
1297 void ListThisUser(struct ctdluser *usbuf, void *data)
1298 {
1299         if (usbuf->axlevel > 0) {
1300                 if ((CC->user.axlevel >= 6)
1301                     || ((usbuf->flags & US_UNLISTED) == 0)
1302                     || ((CC->internal_pgm))) {
1303                         cprintf("%s|%d|%ld|%ld|%ld|%ld|",
1304                                 usbuf->fullname,
1305                                 usbuf->axlevel,
1306                                 usbuf->usernum,
1307                                 (long)usbuf->lastcall,
1308                                 usbuf->timescalled,
1309                                 usbuf->posted);
1310                         if (CC->user.axlevel >= 6)
1311                                 cprintf("%s", usbuf->password);
1312                         cprintf("\n");
1313                 }
1314         }
1315 }
1316
1317 /* 
1318  *  List users
1319  */
1320 void cmd_list(void)
1321 {
1322         cprintf("%d \n", LISTING_FOLLOWS);
1323         ForEachUser(ListThisUser, NULL);
1324         cprintf("000\n");
1325 }
1326
1327
1328
1329
1330 /*
1331  * assorted info we need to check at login
1332  */
1333 void cmd_chek(void)
1334 {
1335         int mail = 0;
1336         int regis = 0;
1337         int vali = 0;
1338
1339         if (CtdlAccessCheck(ac_logged_in)) {
1340                 return;
1341         }
1342
1343         getuser(&CC->user, CC->curr_user);      /* no lock is needed here */
1344         if ((REGISCALL != 0) && ((CC->user.flags & US_REGIS) == 0))
1345                 regis = 1;
1346
1347         if (CC->user.axlevel >= 6) {
1348                 get_control();
1349                 if (CitControl.MMflags & MM_VALID)
1350                         vali = 1;
1351         }
1352
1353         /* check for mail */
1354         mail = InitialMailCheck();
1355
1356         cprintf("%d %d|%d|%d|%s|\n", CIT_OK, mail, regis, vali, CC->cs_inet_email);
1357 }
1358
1359
1360 /*
1361  * check to see if a user exists
1362  */
1363 void cmd_qusr(char *who)
1364 {
1365         struct ctdluser usbuf;
1366
1367         if (getuser(&usbuf, who) == 0) {
1368                 cprintf("%d %s\n", CIT_OK, usbuf.fullname);
1369         } else {
1370                 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1371         }
1372 }
1373
1374
1375 /*
1376  * Administrative Get User Parameters
1377  */
1378 void cmd_agup(char *cmdbuf)
1379 {
1380         struct ctdluser usbuf;
1381         char requested_user[SIZ];
1382
1383         if (CtdlAccessCheck(ac_aide)) {
1384                 return;
1385         }
1386
1387         extract(requested_user, cmdbuf, 0);
1388         if (getuser(&usbuf, requested_user) != 0) {
1389                 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1390                 return;
1391         }
1392         cprintf("%d %s|%s|%u|%ld|%ld|%d|%ld|%ld|%d\n",
1393                 CIT_OK,
1394                 usbuf.fullname,
1395                 usbuf.password,
1396                 usbuf.flags,
1397                 usbuf.timescalled,
1398                 usbuf.posted,
1399                 (int) usbuf.axlevel,
1400                 usbuf.usernum,
1401                 (long)usbuf.lastcall,
1402                 usbuf.USuserpurge);
1403 }
1404
1405
1406
1407 /*
1408  * Administrative Set User Parameters
1409  */
1410 void cmd_asup(char *cmdbuf)
1411 {
1412         struct ctdluser usbuf;
1413         char requested_user[SIZ];
1414         char notify[SIZ];
1415         int np;
1416         int newax;
1417         int deleted = 0;
1418
1419         if (CtdlAccessCheck(ac_aide))
1420                 return;
1421
1422         extract(requested_user, cmdbuf, 0);
1423         if (lgetuser(&usbuf, requested_user) != 0) {
1424                 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1425                 return;
1426         }
1427         np = num_parms(cmdbuf);
1428         if (np > 1)
1429                 extract(usbuf.password, cmdbuf, 1);
1430         if (np > 2)
1431                 usbuf.flags = extract_int(cmdbuf, 2);
1432         if (np > 3)
1433                 usbuf.timescalled = extract_int(cmdbuf, 3);
1434         if (np > 4)
1435                 usbuf.posted = extract_int(cmdbuf, 4);
1436         if (np > 5) {
1437                 newax = extract_int(cmdbuf, 5);
1438                 if ((newax >= 0) && (newax <= 6)) {
1439                         usbuf.axlevel = extract_int(cmdbuf, 5);
1440                 }
1441         }
1442         if (np > 7) {
1443                 usbuf.lastcall = extract_long(cmdbuf, 7);
1444         }
1445         if (np > 8) {
1446                 usbuf.USuserpurge = extract_int(cmdbuf, 8);
1447         }
1448         lputuser(&usbuf);
1449         if (usbuf.axlevel == 0) {
1450                 if (purge_user(requested_user) == 0) {
1451                         deleted = 1;
1452                 }
1453         }
1454
1455         if (deleted) {
1456                 sprintf(notify, "User <%s> deleted by %s\n",
1457                         usbuf.fullname, CC->user.fullname);
1458                 aide_message(notify);
1459         }
1460
1461         cprintf("%d Ok", CIT_OK);
1462         if (deleted)
1463                 cprintf(" (%s deleted)", requested_user);
1464         cprintf("\n");
1465 }
1466
1467
1468
1469 /*
1470  * Check to see if the user who we just sent mail to is logged in.  If yes,
1471  * bump the 'new mail' counter for their session.  That enables them to
1472  * receive a new mail notification without having to hit the database.
1473  */
1474 void BumpNewMailCounter(long which_user) {
1475         struct CitContext *ptr;
1476
1477         begin_critical_section(S_SESSION_TABLE);
1478
1479         for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
1480                 if (ptr->user.usernum == which_user) {
1481                         ptr->newmail += 1;
1482                 }
1483         }
1484
1485         end_critical_section(S_SESSION_TABLE);
1486 }
1487
1488
1489 /*
1490  * Count the number of new mail messages the user has
1491  */
1492 int NewMailCount()
1493 {
1494         int num_newmsgs = 0;
1495
1496         num_newmsgs = CC->newmail;
1497         CC->newmail = 0;
1498
1499         return (num_newmsgs);
1500 }
1501
1502
1503 /*
1504  * Count the number of new mail messages the user has
1505  */
1506 int InitialMailCheck()
1507 {
1508         int num_newmsgs = 0;
1509         int a;
1510         char mailboxname[ROOMNAMELEN];
1511         struct ctdlroom mailbox;
1512         struct visit vbuf;
1513         struct cdbdata *cdbfr;
1514         long *msglist = NULL;
1515         int num_msgs = 0;
1516
1517         MailboxName(mailboxname, sizeof mailboxname, &CC->user, MAILROOM);
1518         if (getroom(&mailbox, mailboxname) != 0)
1519                 return (0);
1520         CtdlGetRelationship(&vbuf, &CC->user, &mailbox);
1521
1522         cdbfr = cdb_fetch(CDB_MSGLISTS, &mailbox.QRnumber, sizeof(long));
1523
1524         if (cdbfr != NULL) {
1525                 msglist = mallok(cdbfr->len);
1526                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1527                 num_msgs = cdbfr->len / sizeof(long);
1528                 cdb_free(cdbfr);
1529         }
1530         if (num_msgs > 0)
1531                 for (a = 0; a < num_msgs; ++a) {
1532                         if (msglist[a] > 0L) {
1533                                 if (msglist[a] > vbuf.v_lastseen) {
1534                                         ++num_newmsgs;
1535                                 }
1536                         }
1537                 }
1538         if (msglist != NULL)
1539                 phree(msglist);
1540
1541         return (num_newmsgs);
1542 }
1543
1544
1545
1546 /*
1547  * Set the preferred view for the current user/room combination
1548  */
1549 void cmd_view(char *cmdbuf) {
1550         int requested_view;
1551         struct visit vbuf;
1552
1553         if (CtdlAccessCheck(ac_logged_in)) {
1554                 return;
1555         }
1556
1557         requested_view = extract_int(cmdbuf, 0);
1558
1559         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
1560         vbuf.v_view = requested_view;
1561         CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
1562         
1563         cprintf("%d ok\n", CIT_OK);
1564 }