d8c09642d6b9915690802f7d32af7feb8d6eb4d2
[citadel.git] / citadel / user_ops.c
1 /* $Id$ */
2
3 #include "sysdep.h"
4 #include <errno.h>
5 #include <stdlib.h>
6 #include <unistd.h>
7 #include <stdio.h>
8 #include <fcntl.h>
9 #include <signal.h>
10 #include <pwd.h>
11 #include <sys/types.h>
12 #include <sys/wait.h>
13 #include <sys/time.h>
14 #include <string.h>
15 #include <syslog.h>
16 #include <limits.h>
17 #ifndef ENABLE_CHKPWD
18 #include "auth.h"
19 #endif
20 #include "citadel.h"
21 #include "server.h"
22 #include "database.h"
23 #include "user_ops.h"
24 #include "sysdep_decls.h"
25 #include "support.h"
26 #include "room_ops.h"
27 #include "logging.h"
28 #include "file_ops.h"
29 #include "control.h"
30 #include "msgbase.h"
31 #include "config.h"
32 #include "dynloader.h"
33 #include "tools.h"
34
35
36 /*
37  * getuser()  -  retrieve named user into supplied buffer.
38  *               returns 0 on success
39  */
40 int getuser(struct usersupp *usbuf, char name[]) {
41
42         char lowercase_name[32];
43         int a;
44         struct cdbdata *cdbus;
45
46         memset(usbuf, 0, sizeof(struct usersupp));
47         for (a=0; a<=strlen(name); ++a) {
48                 if (a < sizeof(lowercase_name))
49                         lowercase_name[a] = tolower(name[a]);
50                 }
51         lowercase_name[sizeof(lowercase_name)-1] = 0;
52
53         cdbus = cdb_fetch(CDB_USERSUPP, lowercase_name, strlen(lowercase_name));
54         if (cdbus == NULL) {
55                 return(1);      /* user not found */
56                 }
57
58         memcpy(usbuf, cdbus->ptr,
59                 ( (cdbus->len > sizeof(struct usersupp)) ?
60                 sizeof(struct usersupp) : cdbus->len) );
61         cdb_free(cdbus);
62
63         if (usbuf->version < 573) {
64                 CC->usersupp.moderation_filter = config.c_default_filter;
65         }
66
67         return(0);
68         }
69
70
71 /*
72  * lgetuser()  -  same as getuser() but locks the record
73  */
74 int lgetuser(struct usersupp *usbuf, char *name)
75 {
76         int retcode;
77
78         retcode = getuser(usbuf,name);
79         if (retcode == 0) {
80                 begin_critical_section(S_USERSUPP);
81                 }
82         return(retcode);
83         }
84
85
86 /*
87  * putuser()  -  write user buffer into the correct place on disk
88  */
89 void putuser(struct usersupp *usbuf)
90 {
91         char lowercase_name[32];
92         int a;
93
94         for (a=0; a<=strlen(usbuf->fullname); ++a) {
95                 if (a < sizeof(lowercase_name))
96                         lowercase_name[a] = tolower(usbuf->fullname[a]);
97                 }
98         lowercase_name[sizeof(lowercase_name)-1] = 0;
99
100         usbuf->version = REV_LEVEL;
101         cdb_store(CDB_USERSUPP,
102                 lowercase_name, strlen(lowercase_name),
103                 usbuf, sizeof(struct usersupp));
104
105         }
106
107
108 /*
109  * lputuser()  -  same as putuser() but locks the record
110  */
111 void lputuser(struct usersupp *usbuf) {
112         putuser(usbuf);
113         end_critical_section(S_USERSUPP);
114         }
115
116 /*
117  * Index-generating function used by Ctdl[Get|Set]Relationship
118  */
119 int GenerateRelationshipIndex(  char *IndexBuf,
120                                 long RoomID,
121                                 long RoomGen,
122                                 long UserID) {
123
124         struct {
125                 long iRoomID;
126                 long iRoomGen;
127                 long iUserID;
128                 } TheIndex;
129
130         TheIndex.iRoomID = RoomID;
131         TheIndex.iRoomGen = RoomGen;
132         TheIndex.iUserID = UserID;
133
134         memcpy(IndexBuf, &TheIndex, sizeof(TheIndex));
135         return(sizeof(TheIndex));
136         }
137
138 /*
139  * Define a relationship between a user and a room
140  */
141 void CtdlSetRelationship(struct visit *newvisit,
142                         struct usersupp *rel_user,
143                         struct quickroom *rel_room) {
144
145         char IndexBuf[32];
146         int IndexLen;
147
148         /* We don't use these in Citadel because they're implicit by the
149          * index, but they must be present if the database is exported.
150          */
151         newvisit->v_roomnum = rel_room->QRnumber;
152         newvisit->v_roomgen = rel_room->QRgen;
153         newvisit->v_usernum = rel_user->usernum;
154
155         /* Generate an index */
156         IndexLen = GenerateRelationshipIndex(IndexBuf,
157                 rel_room->QRnumber,
158                 rel_room->QRgen,
159                 rel_user->usernum);
160
161         /* Store the record */
162         cdb_store(CDB_VISIT, IndexBuf, IndexLen,
163                 newvisit, sizeof(struct visit)
164                 );
165         }
166
167 /*
168  * Locate a relationship between a user and a room
169  */
170 void CtdlGetRelationship(struct visit *vbuf,
171                         struct usersupp *rel_user,
172                         struct quickroom *rel_room) {
173
174         char IndexBuf[32];
175         int IndexLen;
176         struct cdbdata *cdbvisit;
177
178         /* Generate an index */
179         IndexLen = GenerateRelationshipIndex(IndexBuf,
180                 rel_room->QRnumber,
181                 rel_room->QRgen,
182                 rel_user->usernum);
183
184         /* Clear out the buffer */
185         memset(vbuf, 0, sizeof(struct visit));
186
187         cdbvisit = cdb_fetch(CDB_VISIT, IndexBuf, IndexLen);
188         if (cdbvisit != NULL) {
189                 memcpy(vbuf, cdbvisit->ptr,
190                         ( (cdbvisit->len > sizeof(struct visit)) ?
191                         sizeof(struct visit) : cdbvisit->len) );
192                 cdb_free(cdbvisit);
193                 return;
194                 }
195         }
196
197
198 void MailboxName(char *buf, struct usersupp *who, char *prefix) {
199         sprintf(buf, "%010ld.%s", who->usernum, prefix);
200         }
201
202         
203 /*
204  * Is the user currently logged in an Aide?
205  */
206 int is_aide(void) {
207         if (CC->usersupp.axlevel >= 6) return(1);
208         else return(0);
209         }
210
211
212 /*
213  * Is the user currently logged in an Aide *or* the room aide for this room?
214  */
215 int is_room_aide(void) {
216         if ( (CC->usersupp.axlevel >= 6)
217            || (CC->quickroom.QRroomaide == CC->usersupp.usernum) ) {
218                 return(1);
219                 }
220         else {
221                 return(0);
222                 }
223         }
224
225 /*
226  * getuserbynumber()  -  get user by number
227  *                       returns 0 if user was found
228  */
229 int getuserbynumber(struct usersupp *usbuf, long int number)
230 {
231         struct cdbdata *cdbus;
232
233         cdb_rewind(CDB_USERSUPP);
234
235         while(cdbus = cdb_next_item(CDB_USERSUPP), cdbus != NULL) {
236                 memset(usbuf, 0, sizeof(struct usersupp));
237                 memcpy(usbuf, cdbus->ptr,
238                         ( (cdbus->len > sizeof(struct usersupp)) ?
239                         sizeof(struct usersupp) : cdbus->len) );
240                 cdb_free(cdbus);
241                 if (usbuf->usernum == number) {
242                         return(0);
243                         }
244                 }
245         return(-1);
246         }
247
248
249 /*
250  * Back end for cmd_user() and its ilk
251  */
252 int CtdlLoginExistingUser(char *username)
253 {
254         char autoname[256];
255         int found_user = 0;
256         struct passwd *p;
257         int a;
258
259         username[25] = 0;
260         strproc(username);
261
262         if ((CC->logged_in)) {
263                 return login_already_logged_in;
264         }
265
266         found_user = getuser(&CC->usersupp,username);
267         if (found_user != 0) {
268                 p = (struct passwd *)getpwnam(username);
269                 if (p!=NULL) {
270                         strcpy(autoname,p->pw_gecos);
271                         for (a=0; a<strlen(autoname); ++a)
272                                 if (autoname[a]==',') autoname[a]=0;
273                         found_user = getuser(&CC->usersupp,autoname);
274                 }
275         }
276         if (found_user == 0) {
277                 if (((CC->nologin)) && (CC->usersupp.axlevel < 6)) {
278                         return login_too_many_users;
279                 }
280                 else {
281                         strcpy(CC->curr_user,CC->usersupp.fullname);
282                         return login_ok;
283                 }
284         }
285         return login_not_found;
286 }
287
288
289
290 /*
291  * USER cmd
292  */
293 void cmd_user(char *cmdbuf)
294 {
295         char username[256];
296         int a;
297
298         extract(username,cmdbuf,0);
299         username[25] = 0;
300         strproc(username);
301
302         a = CtdlLoginExistingUser(username);
303         switch(a) {
304                 case login_already_logged_in:
305                         cprintf("%d Already logged in.\n",ERROR);
306                         return;
307                 case login_too_many_users:
308                         cprintf("%d %s: "
309                                 "Too many users are already online "
310                                 "(maximum is %d)\n",
311                                 ERROR+MAX_SESSIONS_EXCEEDED,
312                                 config.c_nodename,config.c_maxsessions);
313                         return;
314                 case login_ok:
315                         cprintf("%d Password required for %s\n",
316                                 MORE_DATA,CC->curr_user);
317                         return;
318                 case login_not_found:
319                         cprintf("%d %s not found.\n", ERROR, username);
320                         return;
321                 cprintf("%d Internal error\n", ERROR);
322         }
323 }
324
325
326
327 /*
328  * session startup code which is common to both cmd_pass() and cmd_newu()
329  */
330 void session_startup(void) {
331         syslog(LOG_NOTICE,"user <%s> logged in",CC->curr_user);
332
333         lgetuser(&CC->usersupp,CC->curr_user);
334         ++(CC->usersupp.timescalled);
335         CC->fake_username[0] = '\0';
336         CC->fake_postname[0] = '\0';
337         CC->fake_hostname[0] = '\0';
338         CC->fake_roomname[0] = '\0';
339         time(&CC->usersupp.lastcall);
340
341         /* If this user's name is the name of the system administrator
342          * (as specified in setup), automatically assign access level 6.
343          */
344         if (!strcasecmp(CC->usersupp.fullname, config.c_sysadm)) {
345                 CC->usersupp.axlevel = 6;
346                 }
347
348         lputuser(&CC->usersupp);
349
350         /* Run any cleanup routines registered by loadable modules */
351         PerformSessionHooks(EVT_LOGIN);
352
353         usergoto(BASEROOM,0);           /* Enter the lobby */   
354         rec_log(CL_LOGIN,CC->curr_user);
355         }
356
357
358 void logged_in_response(void) {
359         cprintf("%d %s|%d|%d|%d|%u|%ld\n",
360                 OK, CC->usersupp.fullname, CC->usersupp.axlevel,
361                 CC->usersupp.timescalled, CC->usersupp.posted,
362                 CC->usersupp.flags,
363                 CC->usersupp.usernum);
364 }
365
366
367
368 /* 
369  * misc things to be taken care of when a user is logged out
370  */
371 void logout(struct CitContext *who)
372 {
373         who->logged_in = 0;
374         if (who->download_fp != NULL) {
375                 fclose(who->download_fp);
376                 who->download_fp = NULL;
377                 }
378         if (who->upload_fp != NULL) {
379                 abort_upl(who);
380                 }
381
382         /* Do modular stuff... */
383         PerformSessionHooks(EVT_LOGOUT);
384         }
385
386 #ifdef ENABLE_CHKPWD
387 /*
388  * an alternate version of validpw() which executes `chkpwd' instead of
389  * verifying the password directly
390  */
391 static int validpw(uid_t uid, const char *pass)
392 {
393         pid_t pid;
394         int status, pipev[2];
395         char buf[24];
396
397         if (pipe(pipev)) {
398                 lprintf(1, "pipe failed (%s): denying autologin access for "
399                            "uid %u\n", strerror(errno), uid);
400                 return 0;
401                 }
402
403         switch (pid = fork()) {
404             case -1:
405                 lprintf(1, "fork failed (%s): denying autologin access for "
406                            "uid %u\n", strerror(errno), uid);
407                 close(pipev[0]);
408                 close(pipev[1]);
409                 return 0;
410
411             case 0:
412                 close(pipev[1]);
413                 if (dup2(pipev[0], 0) == -1) {
414                         perror("dup2");
415                         exit(1);
416                         }
417                 close(pipev[0]);
418
419                 execl(BBSDIR "/chkpwd", BBSDIR "/chkpwd", NULL);
420                 perror(BBSDIR "/chkpwd");
421                 exit(1);
422                 }
423
424         close(pipev[0]);
425         write(pipev[1], buf, sprintf(buf, "%lu\n", (unsigned long)uid));
426         write(pipev[1], pass, strlen(pass));
427         write(pipev[1], "\n", 1);
428         close(pipev[1]);
429
430         while (waitpid(pid, &status, 0) == -1)
431                 if (errno != EINTR) {
432                         lprintf(1, "waitpid failed (%s): denying autologin "
433                                    "access for uid %u\n",
434                                 strerror(errno), uid);
435                         return 0;
436                         }
437
438         if (WIFEXITED(status) && !WEXITSTATUS(status))
439                 return 1;
440
441         return 0;
442         }
443 #endif
444
445
446
447 int CtdlTryPassword(char *password)
448 {
449         int code;
450
451         if ((CC->logged_in)) {
452                 return pass_already_logged_in;
453                 }
454         if (!strcmp(CC->curr_user, NLI)) {
455                 return pass_no_user;
456                 }
457         if (getuser(&CC->usersupp, CC->curr_user)) {
458                 return pass_internal_error;
459                 }
460
461         code = (-1);
462         if (CC->usersupp.uid == BBSUID) {
463                 strproc(password);
464                 strproc(CC->usersupp.password);
465                 code = strcasecmp(CC->usersupp.password,password);
466                 }
467 #ifdef ENABLE_AUTOLOGIN
468         else {
469                 if (validpw(CC->usersupp.uid, password)) {
470                         code = 0;
471                         lgetuser(&CC->usersupp, CC->curr_user);
472                         safestrncpy(CC->usersupp.password, password,
473                                     sizeof CC->usersupp.password);
474                         lputuser(&CC->usersupp);
475                         }
476                 }
477 #endif
478
479         if (!code) {
480                 (CC->logged_in) = 1;
481                 session_startup();
482                 return pass_ok;
483                 }
484         else {
485                 rec_log(CL_BADPW,CC->curr_user);
486                 return pass_wrong_password;
487                 }
488         }
489
490
491 void cmd_pass(char *buf)
492 {
493         char password[256];
494         int a;
495
496         extract(password, buf, 0);
497         a = CtdlTryPassword(password);
498
499         switch (a) {
500                 case pass_already_logged_in:
501                         cprintf("%d Already logged in.\n",ERROR);
502                         return;
503                 case pass_no_user:
504                         cprintf("%d You must send a name with USER first.\n",
505                                 ERROR);
506                         return;
507                 case pass_wrong_password:
508                         cprintf("%d Wrong password.\n", ERROR);
509                         return;
510                 case pass_ok:
511                         logged_in_response();
512                         return;
513                 cprintf("%d Can't find user record!\n",
514                         ERROR+INTERNAL_ERROR);
515         }
516 }
517
518
519
520 /*
521  * Delete a user record *and* all of its related resources.
522  */
523 int purge_user(char pname[]) {
524         char filename[64];
525         struct usersupp usbuf;
526         char lowercase_name[32];
527         int a;
528         struct CitContext *ccptr;
529         int user_is_logged_in = 0;
530
531         for (a=0; a<=strlen(pname); ++a) {
532                 lowercase_name[a] = tolower(pname[a]);
533                 }
534
535         if (getuser(&usbuf, pname) != 0) {
536                 lprintf(5, "Cannot purge user <%s> - not found\n", pname);
537                 return(ERROR+NO_SUCH_USER);
538                 }
539
540         /* Don't delete a user who is currently logged in.  Instead, just
541          * set the access level to 0, and let the account get swept up
542          * during the next purge.
543          */
544         user_is_logged_in = 0;
545         begin_critical_section(S_SESSION_TABLE);
546         for (ccptr=ContextList; ccptr!=NULL; ccptr=ccptr->next) {
547                 if (ccptr->usersupp.usernum == usbuf.usernum) {
548                         user_is_logged_in = 1;
549                         }
550                 }
551         end_critical_section(S_SESSION_TABLE);
552         if (user_is_logged_in == 1) {
553                 lprintf(5, "User <%s> is logged in; not deleting.\n", pname);
554                 usbuf.axlevel = 0;
555                 putuser(&usbuf);
556                 return(1);
557                 }
558
559         lprintf(5, "Deleting user <%s>\n", pname);
560
561         /* Perform any purge functions registered by server extensions */
562         PerformUserHooks(usbuf.fullname, usbuf.usernum, EVT_PURGEUSER);
563
564         /* delete any existing user/room relationships */
565         cdb_delete(CDB_VISIT, &usbuf.usernum, sizeof(long));
566
567         /* delete the userlog entry */
568         cdb_delete(CDB_USERSUPP, lowercase_name, strlen(lowercase_name));
569
570         /* remove the user's bio file */        
571         sprintf(filename, "./bio/%ld", usbuf.usernum);
572         unlink(filename);
573
574         /* remove the user's picture */
575         sprintf(filename, "./userpics/%ld.gif", usbuf.usernum);
576         unlink(filename);
577
578         return(0);
579         }
580
581
582 /*
583  * create_user()  -  back end processing to create a new user
584  */
585 int create_user(char *newusername)
586 {
587         struct usersupp usbuf;
588         int a;
589         struct passwd *p = NULL;
590         char username[64];
591         char mailboxname[ROOMNAMELEN];
592
593         strcpy(username, newusername);
594         strproc(username);
595
596 #ifdef ENABLE_AUTOLOGIN
597         p = (struct passwd *)getpwnam(username);
598 #endif
599         if (p != NULL) {
600                 strcpy(username, p->pw_gecos);
601                 for (a=0; a<strlen(username); ++a) {
602                         if (username[a] == ',') username[a] = 0;
603                         }
604                 CC->usersupp.uid = p->pw_uid;
605                 }
606         else {
607                 CC->usersupp.uid = BBSUID;
608                 }
609
610         if (!getuser(&usbuf,username)) {
611                 return(ERROR+ALREADY_EXISTS);
612                 }
613
614         strcpy(CC->curr_user,username);
615         strcpy(CC->usersupp.fullname,username);
616         strcpy(CC->usersupp.password,"");
617         (CC->logged_in) = 1;
618
619         /* These are the default flags on new accounts */
620         CC->usersupp.flags = US_LASTOLD|US_DISAPPEAR|US_PAGINATOR|US_FLOORS;
621
622         CC->usersupp.timescalled = 0;
623         CC->usersupp.posted = 0;
624         CC->usersupp.axlevel = config.c_initax;
625         CC->usersupp.USscreenwidth = 80;
626         CC->usersupp.USscreenheight = 24;
627         time(&CC->usersupp.lastcall);
628         CC->usersupp.moderation_filter = config.c_default_filter;
629
630         /* fetch a new user number */
631         CC->usersupp.usernum = get_new_user_number();
632
633         if (CC->usersupp.usernum == 1L) {
634                 CC->usersupp.axlevel = 6;
635                 }
636
637         /* add user to userlog */
638         putuser(&CC->usersupp);
639         if (getuser(&CC->usersupp,CC->curr_user)) {
640                 return(ERROR+INTERNAL_ERROR);
641                 }
642
643         /* give the user a private mailbox */
644         MailboxName(mailboxname, &CC->usersupp, MAILROOM);
645         create_room(mailboxname, 4, "", 0);
646
647         rec_log(CL_NEWUSER,CC->curr_user);
648         return(0);
649         }
650
651
652
653
654 /*
655  * cmd_newu()  -  create a new user account
656  */
657 void cmd_newu(char *cmdbuf)
658 {
659         int a;
660         char username[256];
661
662         if ((CC->logged_in)) {
663                 cprintf("%d Already logged in.\n",ERROR);
664                 return;
665                 }
666
667         if ((CC->nologin)) {
668                 cprintf("%d %s: Too many users are already online (maximum is %d)\n",
669                 ERROR+MAX_SESSIONS_EXCEEDED,
670                 config.c_nodename,config.c_maxsessions);
671                 }
672
673         extract(username,cmdbuf,0);
674         username[25] = 0;
675         strproc(username);
676
677         if (strlen(username)==0) {
678                 cprintf("%d You must supply a user name.\n",ERROR);
679                 return;
680                 }
681
682         a = create_user(username);
683         if ((!strcasecmp(username, "bbs")) ||
684             (!strcasecmp(username, "new")) ||
685             (!strcasecmp(username, ".")))
686         {
687            cprintf("%d '%s' is an invalid login name.\n", ERROR);
688            return;
689         }
690         if (a==ERROR+ALREADY_EXISTS) {
691                 cprintf("%d '%s' already exists.\n",
692                         ERROR+ALREADY_EXISTS,username);
693                 return;
694                 }
695         else if (a==ERROR+INTERNAL_ERROR) {
696                 cprintf("%d Internal error - user record disappeared?\n",
697                         ERROR+INTERNAL_ERROR);
698                 return;
699                 }
700         else if (a==0) {
701                 session_startup();
702                 logged_in_response();
703                 }
704         else {
705                 cprintf("%d unknown error\n",ERROR);
706                 }
707         rec_log(CL_NEWUSER,CC->curr_user);
708         }
709
710
711
712 /*
713  * set password
714  */
715 void cmd_setp(char *new_pw)
716 {
717         if (!(CC->logged_in)) {
718                 cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
719                 return;
720                 }
721         if (CC->usersupp.uid != BBSUID) {
722                 cprintf("%d Not allowed.  Use the 'passwd' command.\n",ERROR);
723                 return;
724                 }
725         strproc(new_pw);
726         if (strlen(new_pw)==0) {
727                 cprintf("%d Password unchanged.\n",OK);
728                 return;
729                 }
730         lgetuser(&CC->usersupp,CC->curr_user);
731         strcpy(CC->usersupp.password,new_pw);
732         lputuser(&CC->usersupp);
733         cprintf("%d Password changed.\n",OK);
734         rec_log(CL_PWCHANGE,CC->curr_user);
735         PerformSessionHooks(EVT_SETPASS);
736         }
737
738 /*
739  * get user parameters
740  */
741 void cmd_getu(void) {
742         if (!(CC->logged_in)) {
743                 cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
744                 return;
745                 }
746         getuser(&CC->usersupp,CC->curr_user);
747         cprintf("%d %d|%d|%d|%d\n",
748                 OK,
749                 CC->usersupp.USscreenwidth,
750                 CC->usersupp.USscreenheight,
751                 (CC->usersupp.flags & US_USER_SET),
752                 CC->usersupp.moderation_filter
753                 );
754         }
755
756 /*
757  * set user parameters
758  */
759 void cmd_setu(char *new_parms)
760 {
761         int new_mod;
762
763         if (num_parms(new_parms) < 3) {
764                 cprintf("%d Usage error.\n",ERROR);
765                 return;
766                 }       
767         if (!(CC->logged_in)) {
768                 cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
769                 return;
770                 }
771         lgetuser(&CC->usersupp,CC->curr_user);
772         CC->usersupp.USscreenwidth = extract_int(new_parms,0);
773         CC->usersupp.USscreenheight = extract_int(new_parms,1);
774         CC->usersupp.flags = CC->usersupp.flags & (~US_USER_SET);
775         CC->usersupp.flags = CC->usersupp.flags | 
776                 (extract_int(new_parms,2) & US_USER_SET);
777
778         if (num_parms(new_parms) >= 4) {
779                 new_mod = extract_int(new_parms, 3);
780                 lprintf(9, "new_mod extracted to %d\n", new_mod);
781
782                 /* Aides cannot set the filter level lower than -100 */ 
783                 if (new_mod < (-100) ) new_mod = -100;
784
785                 /* Normal users cannot set the filter level lower than -63 */
786                 if ( (new_mod < (-63)) && (CC->usersupp.axlevel < 6) )
787                         new_mod = -63;
788
789                 /* Nobody can set the filter level higher than +63 */
790                 if (new_mod > 63) new_mod = 63;
791
792                 CC->usersupp.moderation_filter = new_mod;
793                 lprintf(9, "new_mod processed to %d\n", new_mod);
794         }
795
796         lputuser(&CC->usersupp);
797         cprintf("%d Ok\n",OK);
798         }
799
800 /*
801  * set last read pointer
802  */
803 void cmd_slrp(char *new_ptr)
804 {
805         long newlr;
806         struct visit vbuf;
807
808         if (!(CC->logged_in)) {
809                 cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
810                 return;
811                 }
812
813         if (!strncasecmp(new_ptr,"highest",7)) {
814                 newlr = CC->quickroom.QRhighest;
815                 }
816         else {
817                 newlr = atol(new_ptr);
818                 }
819
820         lgetuser(&CC->usersupp, CC->curr_user);
821
822         CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
823         vbuf.v_lastseen = newlr;
824         CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
825
826         lputuser(&CC->usersupp);
827         cprintf("%d %ld\n",OK,newlr);
828         }
829
830
831 /*
832  * INVT and KICK commands
833  */
834 void cmd_invt_kick(char *iuser, int op)
835                         /* user name */
836         {               /* 1 = invite, 0 = kick out */
837         struct usersupp USscratch;
838         char bbb[256];
839         struct visit vbuf;
840
841         if (!(CC->logged_in)) {
842                 cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
843                 return;
844                 }
845
846         if (is_room_aide()==0) {
847                 cprintf("%d Higher access required.\n",
848                         ERROR+HIGHER_ACCESS_REQUIRED);
849                 return;
850                 }
851
852         if (lgetuser(&USscratch,iuser)!=0) {
853                 cprintf("%d No such user.\n",ERROR);
854                 return;
855                 }
856
857         CtdlGetRelationship(&vbuf, &USscratch, &CC->quickroom);
858
859         if (op==1) {
860                 vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT;
861                 vbuf.v_flags = vbuf.v_flags | V_ACCESS;
862                 }
863
864         if (op==0) {
865                 vbuf.v_flags = vbuf.v_flags & ~V_ACCESS;
866                 vbuf.v_flags = vbuf.v_flags | V_FORGET | V_LOCKOUT;
867                 }
868
869         CtdlSetRelationship(&vbuf, &USscratch, &CC->quickroom);
870
871         lputuser(&USscratch);
872
873         /* post a message in Aide> saying what we just did */
874         sprintf(bbb,"%s %s %s> by %s\n",
875                 iuser,
876                 ((op == 1) ? "invited to" : "kicked out of"),
877                 CC->quickroom.QRname,
878                 CC->usersupp.fullname);
879         aide_message(bbb);
880
881         cprintf("%d %s %s %s.\n",
882                 OK, iuser,
883                 ((op == 1) ? "invited to" : "kicked out of"),
884                 CC->quickroom.QRname);
885         return;
886         }
887
888
889 /*
890  * forget (Zap) the current room
891  */
892 void cmd_forg(void) {
893         struct visit vbuf;
894
895         if (!(CC->logged_in)) {
896                 cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
897                 return;
898                 }
899
900         if (is_aide()) {
901                 cprintf("%d Aides cannot forget rooms.\n",ERROR);
902                 return;
903                 }
904
905         lgetuser(&CC->usersupp,CC->curr_user);
906         CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
907
908         vbuf.v_flags = vbuf.v_flags | V_FORGET;
909         vbuf.v_flags = vbuf.v_flags & ~V_ACCESS;
910
911         CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
912         lputuser(&CC->usersupp);
913         cprintf("%d Ok\n",OK);
914         usergoto(BASEROOM, 0);
915         }
916
917 /*
918  * Get Next Unregistered User
919  */
920 void cmd_gnur(void) {
921         struct cdbdata *cdbus;
922         struct usersupp usbuf;
923
924         if (!(CC->logged_in)) {
925                 cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
926                 return;
927                 }
928
929         if (CC->usersupp.axlevel < 6) {
930                 cprintf("%d Higher access required.\n",
931                         ERROR+HIGHER_ACCESS_REQUIRED);
932                 return;
933                 }
934
935         if ((CitControl.MMflags&MM_VALID)==0) {
936                 cprintf("%d There are no unvalidated users.\n",OK);
937                 return;
938                 }
939
940         /* There are unvalidated users.  Traverse the usersupp database,
941          * and return the first user we find that needs validation.
942          */
943         cdb_rewind(CDB_USERSUPP);
944         while (cdbus = cdb_next_item(CDB_USERSUPP), cdbus != NULL) {
945                 memset(&usbuf, 0, sizeof(struct usersupp));
946                 memcpy(&usbuf, cdbus->ptr,
947                         ( (cdbus->len > sizeof(struct usersupp)) ?
948                         sizeof(struct usersupp) : cdbus->len) );
949                 cdb_free(cdbus);
950                 if ((usbuf.flags & US_NEEDVALID)
951                    &&(usbuf.axlevel > 0)) {
952                         cprintf("%d %s\n",MORE_DATA,usbuf.fullname);
953                         return;
954                         }
955                 } 
956
957         /* If we get to this point, there are no more unvalidated users.
958          * Therefore we clear the "users need validation" flag.
959          */
960
961         begin_critical_section(S_CONTROL);
962         get_control();
963         CitControl.MMflags = CitControl.MMflags&(~MM_VALID);
964         put_control();
965         end_critical_section(S_CONTROL);
966         cprintf("%d *** End of registration.\n",OK);
967
968
969         }
970
971
972 /*
973  * validate a user
974  */
975 void cmd_vali(char *v_args)
976 {
977         char user[256];
978         int newax;
979         struct usersupp userbuf;
980
981         extract(user,v_args,0);
982         newax = extract_int(v_args,1);
983
984         if (!(CC->logged_in)) {
985                 cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
986                 return;
987                 }
988
989         if (CC->usersupp.axlevel < 6) {
990                 cprintf("%d Higher access required.\n",
991                         ERROR+HIGHER_ACCESS_REQUIRED);
992                 return;
993                 }
994
995         if (lgetuser(&userbuf,user)!=0) {
996                 cprintf("%d '%s' not found.\n",ERROR+NO_SUCH_USER,user);
997                 return;
998                 }
999
1000         userbuf.axlevel = newax;
1001         userbuf.flags = (userbuf.flags & ~US_NEEDVALID);
1002
1003         lputuser(&userbuf);
1004
1005         /* If the access level was set to zero, delete the user */
1006         if (newax == 0) {
1007                 if (purge_user(user)==0) {
1008                         cprintf("%d %s Deleted.\n", OK, userbuf.fullname);
1009                         return;
1010                         }
1011                 }
1012
1013         cprintf("%d ok\n",OK);
1014         }
1015
1016
1017
1018 /* 
1019  *  Traverse the user file...
1020  */
1021 void ForEachUser(void (*CallBack)(struct usersupp *EachUser, void *out_data),
1022                 void *in_data) {
1023         struct usersupp usbuf;
1024         struct cdbdata *cdbus;
1025
1026         cdb_rewind(CDB_USERSUPP);
1027
1028         while(cdbus = cdb_next_item(CDB_USERSUPP), cdbus != NULL) {
1029                 memset(&usbuf, 0, sizeof(struct usersupp));
1030                 memcpy(&usbuf, cdbus->ptr,
1031                         ( (cdbus->len > sizeof(struct usersupp)) ?
1032                         sizeof(struct usersupp) : cdbus->len) );
1033                 cdb_free(cdbus);
1034                 (*CallBack)(&usbuf, in_data);
1035                 }
1036         }
1037
1038
1039 /*
1040  * List one user (this works with cmd_list)
1041  */
1042 void ListThisUser(struct usersupp *usbuf, void *data) {
1043         if (usbuf->axlevel > 0) {
1044                 if ((CC->usersupp.axlevel>=6)
1045                    ||((usbuf->flags&US_UNLISTED)==0)
1046                    ||((CC->internal_pgm))) {
1047                         cprintf("%s|%d|%ld|%ld|%d|%d|",
1048                                 usbuf->fullname,
1049                                 usbuf->axlevel,
1050                                 usbuf->usernum,
1051                                 usbuf->lastcall,
1052                                 usbuf->timescalled,
1053                                 usbuf->posted);
1054                         if (CC->usersupp.axlevel >= 6)
1055                                 cprintf("%s",usbuf->password);
1056                         cprintf("\n");
1057                         }
1058                 }
1059         }
1060
1061 /* 
1062  *  List users
1063  */
1064 void cmd_list(void) {
1065         cprintf("%d \n",LISTING_FOLLOWS);
1066         ForEachUser(ListThisUser, NULL);
1067         cprintf("000\n");
1068         }
1069
1070
1071
1072
1073 /*
1074  * assorted info we need to check at login
1075  */
1076 void cmd_chek(void) {
1077         int mail = 0;
1078         int regis = 0;
1079         int vali = 0;
1080         
1081         if (!(CC->logged_in)) {
1082                 cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
1083                 return;
1084                 }
1085
1086         getuser(&CC->usersupp,CC->curr_user); /* no lock is needed here */
1087         if ((REGISCALL!=0)&&((CC->usersupp.flags&US_REGIS)==0)) regis = 1;
1088
1089         if (CC->usersupp.axlevel >= 6) {
1090                 get_control();
1091                 if (CitControl.MMflags&MM_VALID) vali = 1;
1092                 }
1093
1094
1095         /* check for mail */
1096         mail = NewMailCount();
1097
1098         cprintf("%d %d|%d|%d\n",OK,mail,regis,vali);
1099         }
1100
1101
1102 /*
1103  * check to see if a user exists
1104  */
1105 void cmd_qusr(char *who)
1106 {
1107         struct usersupp usbuf;
1108
1109         if (getuser(&usbuf,who) == 0) {
1110                 cprintf("%d %s\n",OK,usbuf.fullname);
1111                 }
1112         else {
1113                 cprintf("%d No such user.\n",ERROR+NO_SUCH_USER);
1114                 }
1115         }
1116
1117
1118 /*
1119  * Administrative Get User Parameters
1120  */
1121 void cmd_agup(char *cmdbuf) {
1122         struct usersupp usbuf;
1123         char requested_user[256];
1124
1125         if ( (CC->internal_pgm==0)
1126            && ( (CC->logged_in == 0) || (is_aide()==0) ) ) {
1127                 cprintf("%d Higher access required.\n", 
1128                         ERROR + HIGHER_ACCESS_REQUIRED);
1129                 return;
1130                 }
1131
1132         extract(requested_user, cmdbuf, 0);
1133         if (getuser(&usbuf, requested_user) != 0) {
1134                 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1135                 return;
1136                 }
1137
1138         cprintf("%d %s|%s|%u|%d|%d|%d|%ld|%ld|%d\n", 
1139                 OK,
1140                 usbuf.fullname,
1141                 usbuf.password,
1142                 usbuf.flags,
1143                 usbuf.timescalled,
1144                 usbuf.posted,
1145                 (int)usbuf.axlevel,
1146                 usbuf.usernum,
1147                 usbuf.lastcall,
1148                 usbuf.USuserpurge);
1149         }
1150
1151
1152
1153 /*
1154  * Administrative Set User Parameters
1155  */
1156 void cmd_asup(char *cmdbuf) {
1157         struct usersupp usbuf;
1158         char requested_user[256];
1159         int np;
1160         int newax;
1161         int deleted = 0;
1162         
1163         if ( (CC->internal_pgm==0)
1164            && ( (CC->logged_in == 0) || (is_aide()==0) ) ) {
1165                 cprintf("%d Higher access required.\n", 
1166                         ERROR + HIGHER_ACCESS_REQUIRED);
1167                 return;
1168                 }
1169
1170         extract(requested_user, cmdbuf, 0);
1171         if (lgetuser(&usbuf, requested_user) != 0) {
1172                 cprintf("%d No such user.\n", ERROR + NO_SUCH_USER);
1173                 return;
1174                 }
1175
1176         np = num_parms(cmdbuf);
1177         if (np > 1) extract(usbuf.password, cmdbuf, 1);
1178         if (np > 2) usbuf.flags = extract_int(cmdbuf, 2);
1179         if (np > 3) usbuf.timescalled = extract_int(cmdbuf, 3);
1180         if (np > 4) usbuf.posted = extract_int(cmdbuf, 4);
1181         if (np > 5) {
1182                 newax = extract_int(cmdbuf, 5);
1183                 if ((newax >=0) && (newax <= 6)) {
1184                         usbuf.axlevel = extract_int(cmdbuf, 5);
1185                         }
1186                 }
1187         if (np > 7) {
1188                 usbuf.lastcall = extract_long(cmdbuf, 7);
1189                 }
1190         if (np > 8) {
1191                 usbuf.USuserpurge = extract_int(cmdbuf, 8);
1192                 }
1193
1194         lputuser(&usbuf);
1195         if (usbuf.axlevel == 0) {
1196                 if (purge_user(requested_user)==0) {
1197                         deleted = 1;
1198                         }
1199                 }
1200         cprintf("%d Ok", OK);
1201         if (deleted) cprintf(" (%s deleted)", requested_user);
1202         cprintf("\n");
1203         }
1204
1205
1206 /*
1207  * Count the number of new mail messages the user has
1208  */
1209 int NewMailCount() {
1210         int num_newmsgs = 0;
1211         int a;
1212         char mailboxname[ROOMNAMELEN];
1213         struct quickroom mailbox;
1214         struct visit vbuf;
1215         struct cdbdata *cdbfr;
1216         long *msglist = NULL;
1217         int num_msgs = 0;
1218
1219         MailboxName(mailboxname, &CC->usersupp, MAILROOM);
1220         if (getroom(&mailbox, mailboxname)!=0) return(0);
1221         CtdlGetRelationship(&vbuf, &CC->usersupp, &mailbox);
1222
1223         cdbfr = cdb_fetch(CDB_MSGLISTS, &mailbox.QRnumber, sizeof(long));
1224
1225         if (cdbfr != NULL) {
1226                 msglist = mallok(cdbfr->len);
1227                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1228                 num_msgs = cdbfr->len / sizeof(long);
1229                 cdb_free(cdbfr);
1230         }
1231
1232         if (num_msgs > 0) for (a=0; a<num_msgs; ++a) {
1233                 if (msglist[a]>0L) {
1234                         if (msglist[a] > vbuf.v_lastseen) {
1235                                 ++num_newmsgs;
1236                                 }
1237                         }
1238                 }
1239
1240         if (msglist != NULL) phree(msglist);
1241
1242         return(num_newmsgs);
1243         }