Completed initial version of text client admin edit of user email addresses
[citadel.git] / textclient / src / routines.c
1 /*
2  * Client-side support functions.
3  *
4  * Copyright (c) 1987-2016 by the citadel.org team
5  *
6  *  This program is open source software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License version 3.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  */
14
15 #include "sysdep.h"
16 #include <stdlib.h>
17 #include <unistd.h>
18 #include <fcntl.h>
19 #include <stdio.h>
20 #include <ctype.h>
21 #include <string.h>
22 #include <sys/types.h>
23 #include <sys/ioctl.h>
24 #include <pwd.h>
25 #include <signal.h>
26 #include <dirent.h>
27 #include <errno.h>
28
29 #if TIME_WITH_SYS_TIME
30 # include <sys/time.h>
31 # include <time.h>
32 #else
33 # if HAVE_SYS_TIME_H
34 #  include <sys/time.h>
35 # else
36 #  include <time.h>
37 # endif
38 #endif
39
40 #ifdef HAVE_LIMITS_H
41 #include <limits.h>
42 #endif
43
44 #include <libcitadel.h>
45 #include "citadel_ipc.h"
46 #include "screen.h"
47
48 #define ROUTINES_C
49
50 #include "routines.h"
51 #include "commands.h"
52 #include "citadel_decls.h"
53 #include "routines2.h"
54 #include "tuiconfig.h"
55
56 #define IFAIDE if(axlevel>=AxAideU)
57 #define IFNAIDE if (axlevel<AxAideU)
58
59 extern unsigned userflags;
60 extern char sigcaught;
61 extern char rc_floor_mode;
62 extern int rc_ansi_color;
63 extern int rc_prompt_control;
64
65 /* Destructive backspace */
66 void back(int spaces) {
67         int a;
68         for (a=0; a<spaces; ++a) {
69                 scr_putc(8);
70                 scr_putc(32);
71                 scr_putc(8);
72         }
73 }
74
75 /*
76  * Edit a user's Internet email addresses
77  */
78 void edit_user_internet_email_addresses(CtdlIPC *ipc, char *who)
79 {
80         char buf[SIZ];
81         char *resp = NULL;
82         int num_recs = 0;
83         char **recs = NULL;
84         char ch;
85         int i, j;
86         int quitting = 0;
87         int modified = 0;
88         int r;
89         char emailaddrs[512];
90
91         r = CtdlIPCAideGetEmailAddresses(ipc, who, emailaddrs, buf);
92         if (r / 100 == 1) {
93                 while (!IsEmptyStr(emailaddrs)) {
94                         extract_token(buf, emailaddrs, 0, '\n', sizeof buf);
95                         remove_token(emailaddrs, 0, '\n');
96                         if (!IsEmptyStr(buf)) {
97                                 ++num_recs;
98                                 if (num_recs == 1) recs = malloc(sizeof(char *));
99                                 else recs = realloc(recs, (sizeof(char *)) * num_recs);
100                                 recs[num_recs-1] = malloc(strlen(buf) + 1);
101                                 strcpy(recs[num_recs-1], buf);
102                         }
103                 }
104         }
105
106         do {
107                 scr_printf("\n");
108                 color(BRIGHT_WHITE);
109                 scr_printf("    Internet email addresses for %s\n", who);
110                 color(DIM_WHITE);
111                 scr_printf("--- --------------------------------------------------\n");
112                 for (i=0; i<num_recs; ++i) {
113                         color(DIM_WHITE);
114                         scr_printf("%3d ", i+1);
115                         color(BRIGHT_CYAN);
116                         scr_printf("%s\n", recs[i]);
117                         color(DIM_WHITE);
118                 }
119
120                 ch = keymenu("", "<A>dd|<D>elete|<S>ave|<Q>uit");
121                 switch(ch) {
122                         case 'a':
123                                 newprompt("Enter new email address: ", buf, 50);
124                                 striplt(buf);
125                                 if (!IsEmptyStr(buf)) {
126                                         // FIXME validate the email address (format, our own domain, addr does not belong to another user)
127                                         ++num_recs;
128                                         if (num_recs == 1) {
129                                                 recs = malloc(sizeof(char *));
130                                         }
131                                         else {
132                                                 recs = realloc(recs, (sizeof(char *)) * num_recs);
133                                         }
134                                         recs[num_recs-1] = strdup(buf);
135                                 }
136                                 modified = 1;
137                                 break;
138                         case 'd':
139                                 i = intprompt("Delete which address", 1, 1, num_recs) - 1;
140                                 free(recs[i]);
141                                 --num_recs;
142                                 for (j=i; j<num_recs; ++j) {
143                                         recs[j] = recs[j+1];
144                                 }
145                                 modified = 1;
146                                 break;
147                         case 's':
148                                 r = 1;
149                                 for (i = 0; i < num_recs; i++)
150                                         r += 1 + strlen(recs[i]);
151                                 resp = (char *)calloc(1, r);
152                                 if (!resp) {
153                                         scr_printf("Can't save config - out of memory!\n");
154                                         logoff(ipc, 1);
155                                 }
156                                 if (num_recs) for (i = 0; i < num_recs; i++) {
157                                         strcat(resp, recs[i]);
158                                         strcat(resp, "\n");
159                                 }
160                                 r = CtdlIPCAideSetEmailAddresses(ipc, who, resp, buf);
161                                 if (r / 100 != 4) {
162                                         scr_printf("%s\n", buf);
163                                 } else {
164                                         scr_printf("Saved %d addresses.\n", num_recs);
165                                         modified = 0;
166                                         quitting = 1;
167                                 }
168                                 free(resp);
169                                 break;
170                         case 'q':
171                                 quitting = !modified || boolprompt("Quit without saving", 0);
172                                 break;
173                         default:
174                                 break;
175                 }
176         } while (!quitting);
177
178         if (recs != NULL) {
179                 for (i=0; i<num_recs; ++i) free(recs[i]);
180                 free(recs);
181         }
182 }
183
184
185 /*
186  * Edit or delete a user (cmd=25 to edit/create, 96 to delete)
187  */
188 void edituser(CtdlIPC *ipc, int cmd)
189 {
190         char buf[SIZ];
191         char who[USERNAME_SIZE];
192         char newname[USERNAME_SIZE];
193         struct ctdluser *user = NULL;
194         int newnow = 0;
195         int r;                          /* IPC response code */
196         int change_name = 0;
197
198         strcpy(newname, "");
199
200         newprompt("User name: ", who, 29);
201         while ((r = CtdlIPCAideGetUserParameters(ipc, who, &user, buf)) / 100 != 2) {
202                 scr_printf("%s\n", buf);
203                 if (cmd == 25) {
204                         scr_printf("Do you want to create this user? ");
205                         if (yesno()) {
206                                 r = CtdlIPCCreateUser(ipc, who, 0, buf);
207                                 if (r / 100 == 2) {
208                                         newnow = 1;
209                                         continue;
210                                 }
211                                 scr_printf("%s\n", buf);
212                         }
213                 }
214                 free(user);
215                 return;
216         }
217
218         if (cmd == 25) {                // user edit
219
220                 /* val_user(ipc, user->fullname, 0); we used to display the vCard here but there's really no need */
221
222                 if (!newnow) {
223                         change_name = 1;
224                         while (change_name == 1) {
225                                 if (boolprompt("Change name", 0)) {
226                                         strprompt("New name", newname, USERNAME_SIZE-1);
227                                         r = CtdlIPCRenameUser(ipc, user->fullname, newname, buf);
228                                         if (r / 100 != 2) {
229                                                 scr_printf("%s\n", buf);
230                                         }
231                                         else {
232                                                 strcpy(user->fullname, newname);
233                                                 change_name = 0;
234                                         }
235                                 }
236                                 else {
237                                         change_name = 0;
238                                 }
239                         }
240                 }
241
242                 if (newnow || boolprompt("Change password", 0)) {
243                         strprompt("Password", user->password, -19);
244                 }
245         
246                 user->axlevel = intprompt("Access level", user->axlevel, 0, 6);
247                 if (boolprompt("Permission to send Internet mail", (user->flags & US_INTERNET))) {
248                         user->flags |= US_INTERNET;
249                 }
250                 else {
251                         user->flags &= ~US_INTERNET;
252                 }
253                 if (boolprompt("Ask user to register again", !(user->flags & US_REGIS))) {
254                         user->flags &= ~US_REGIS;
255                 }
256                 else {
257                         user->flags |= US_REGIS;
258                 }
259                 user->timescalled = intprompt("Times called", user->timescalled, 0, INT_MAX);
260                 user->posted = intprompt("Messages posted", user->posted, 0, INT_MAX);
261                 user->lastcall = boolprompt("Set last login to now", 0) ?  time(NULL) : user->lastcall;
262                 user->USuserpurge = intprompt("Purge time (in days, 0 for system default", user->USuserpurge, 0, INT_MAX);
263         }
264
265         if (cmd == 96) {
266                 scr_printf("Do you want to delete this user? ");
267                 if (!yesno()) {
268                         free(user);
269                         return;
270                 }
271                 user->axlevel = AxDeleted;
272         }
273
274         r = CtdlIPCAideSetUserParameters(ipc, user, buf);
275         if (r / 100 != 2) {
276                 scr_printf("%s\n", buf);
277         }
278         free(user);
279
280         if (boolprompt("Edit this user's Internet email addresses", 0)) {
281                 edit_user_internet_email_addresses(ipc, who);
282         }
283
284 }
285
286
287 /* Display a prompt and flip a bit based on whether the user answers
288  * yes or no.  Yes=1 and No=0, unless 'backwards' is set to a nonzero value
289  * in which case No=1 and Yes=0.
290  */
291 int set_attr(CtdlIPC *ipc, unsigned int sval, char *prompt, unsigned int sbit, int backwards)
292 {
293         int a;
294         int temp;
295
296         temp = sval;
297         color(DIM_WHITE);
298         scr_printf("%50s ", prompt);
299         color(DIM_MAGENTA);
300         scr_printf("[");
301         color(BRIGHT_MAGENTA);
302
303         if (backwards) {
304                 scr_printf("%3s", ((temp&sbit) ? "No":"Yes"));
305         }
306         else {
307                 scr_printf("%3s", ((temp&sbit) ? "Yes":"No"));
308         }
309
310         color(DIM_MAGENTA);
311         scr_printf("]? ");
312         color(BRIGHT_CYAN);
313         a = (temp & sbit);
314         if (a != 0) a = 1;
315         if (backwards) a = 1 - a;
316         a = yesno_d(a);
317         if (backwards) a = 1 - a;
318         color(DIM_WHITE);
319         temp = (temp|sbit);
320         if (!a) temp = (temp^sbit);
321         return(temp);
322 }
323
324 /*
325  * modes are:  0 - .EC command, 1 - .EC for new user,
326  *             2 - toggle Xpert mode  3 - toggle floor mode
327  */
328 void enter_config(CtdlIPC *ipc, int mode)
329 {
330         char buf[SIZ];
331         struct ctdluser *user = NULL;
332         int r;                          /* IPC response code */
333
334         r = CtdlIPCGetConfig(ipc, &user, buf);
335         if (r / 100 != 2) {
336                 scr_printf("%s\n", buf);
337                 free(user);
338                 return;
339         }
340
341         if (mode == 0 || mode == 1) {
342
343                 user->flags = set_attr(ipc, user->flags,
344                                        "Are you an experienced Citadel user",
345                                        US_EXPERT, 0);
346                 if ((user->flags & US_EXPERT) == 0 && mode == 1) {
347                         free(user);
348                         return;
349                 }
350
351                 user->flags = set_attr(
352                         ipc,
353                         user->flags,
354                         "Print last old message on New message request",
355                         US_LASTOLD,
356                         0
357                 );
358
359                 user->flags = set_attr(
360                         ipc,
361                         user->flags,
362                         "Prompt after each message",
363                         US_NOPROMPT,
364                         1
365                 );
366
367                 if ((user->flags & US_NOPROMPT) == 0) {
368                         user->flags = set_attr(
369                                 ipc,
370                                 user->flags,
371                                 "Use 'disappearing' prompts",
372                                 US_DISAPPEAR,
373                                 0
374                         );
375                 }
376
377                 user->flags = set_attr(
378                         ipc,
379                         user->flags,
380                         "Pause after each screenful of text",
381                         US_PAGINATOR,
382                         0
383                 );
384
385                 if (rc_prompt_control == 3 && (user->flags & US_PAGINATOR)) {
386                         user->flags = set_attr(
387                                 ipc,
388                                 user->flags,
389                                 "<N>ext and <S>top work at paginator prompt",
390                                 US_PROMPTCTL,
391                                 0
392                         );
393                 }
394
395                 if (rc_floor_mode == RC_DEFAULT) {
396                         user->flags = set_attr(
397                                 ipc,
398                                 user->flags,
399                                 "View rooms by floor",
400                                 US_FLOORS,
401                                 0
402                         );
403                 }
404
405                 if (rc_ansi_color == 3) {
406                         user->flags = set_attr(
407                                 ipc,
408                                 user->flags,
409                                 "Enable color support",
410                                 US_COLOR,
411                                 0
412                         );
413                 }
414
415                 if ((user->flags & US_EXPERT) == 0) {
416                         formout(ipc, "unlisted");
417                 }
418
419                 user->flags = set_attr(
420                         ipc,
421                         user->flags,
422                         "Be unlisted in userlog",
423                         US_UNLISTED,
424                         0
425                 );
426
427                 if (!IsEmptyStr(editor_path)) {
428                         user->flags = set_attr(
429                                 ipc,
430                                 user->flags,
431                                 "Always enter messages with the full-screen editor",
432                                 US_EXTEDIT,
433                                 0
434                         );
435                 }
436
437         }
438
439         if (mode == 2) {
440                 if (user->flags & US_EXPERT) {
441                         user->flags ^= US_EXPERT;
442                         scr_printf("Expert mode now OFF\n");
443                 } else {
444                         user->flags |= US_EXPERT;
445                         scr_printf("Expert mode now ON\n");
446                 }
447         }
448
449         if (mode == 3) {
450                 if (user->flags & US_FLOORS) {
451                         user->flags ^= US_FLOORS;
452                         scr_printf("Floor mode now OFF\n");
453                 } else {
454                         user->flags |= US_FLOORS;
455                         scr_printf("Floor mode now ON\n");
456                 }
457         }
458
459         r = CtdlIPCSetConfig(ipc, user, buf);
460         if (r / 100 != 2) scr_printf("%s\n", buf);
461         userflags = user->flags;
462         free(user);
463 }
464
465 /*
466  * getstring()  -  get a line of text from a file
467  *                 ignores lines beginning with "#"
468  */
469 int getstring(FILE *fp, char *string)
470 {
471         int a,c;
472         do {
473                 strcpy(string,"");
474                 a=0;
475                 do {
476                         c=getc(fp);
477                         if (c<0) {
478                                 string[a]=0;
479                                 return(-1);
480                         }
481                         string[a++]=c;
482                 } while(c!=10);
483                         string[a-1]=0;
484         } while(string[0]=='#');
485         return(strlen(string));
486 }
487
488
489 /* Searches for patn in search string */
490 int pattern(char *search, char *patn) {
491         int a,b,len;
492         
493         len = strlen(patn);
494         for (a=0; !IsEmptyStr(&search[a]); ++a) {
495                 b=strncasecmp(&search[a],patn,len);
496                 if (b==0) return(b);
497         }
498         return(-1);
499 }
500
501
502 void strproc(char *string)
503 {
504         int a;
505
506         if (IsEmptyStr(string)) return;
507
508         /* Convert non-printable characters to blanks */
509         for (a=0; !IsEmptyStr(&string[a]); ++a) {
510                 if (string[a]<32) string[a]=32;
511                 if (string[a]>126) string[a]=32;
512         }
513
514         /* Remove leading and trailing blanks */
515         while(string[0]<33) strcpy(string,&string[1]);
516         while(string[strlen(string)-1]<33) string[strlen(string)-1]=0;
517
518         /* Remove double blanks */
519         for (a=0; a<strlen(string); ++a) {
520                 if ((string[a]==32)&&(string[a+1]==32)) {
521                         strcpy(&string[a],&string[a+1]);
522                         a=0;
523                 }
524         }
525
526         /* remove characters which would interfere with the network */
527         for (a=0; a<strlen(string); ++a) {
528                 if (string[a]=='!') strcpy(&string[a],&string[a+1]);
529                 if (string[a]=='@') strcpy(&string[a],&string[a+1]);
530                 if (string[a]=='_') strcpy(&string[a],&string[a+1]);
531                 if (string[a]==',') strcpy(&string[a],&string[a+1]);
532                 if (string[a]=='%') strcpy(&string[a],&string[a+1]);
533                 if (string[a]=='|') strcpy(&string[a],&string[a+1]);
534         }
535
536 }
537
538
539 void progress(CtdlIPC* ipc, unsigned long curr, unsigned long cmax)
540 {
541         static char dots[] =
542                 "**************************************************";
543         char dots_printed[51];
544         char fmt[42];
545         unsigned long a;
546
547         if (curr >= cmax) {
548                 scr_printf("\r%79s\r","");
549         } else {
550                 /* a will be range 0-50 rather than 0-100 */
551                 a=(curr * 50) / cmax;
552                 sprintf(fmt, "[%%s%%%lds] %%3ld%%%% %%10ld/%%10ld\r", 50 - a);
553                 strncpy(dots_printed, dots, a);
554                 dots_printed[a] = 0;
555                 scr_printf(fmt, dots_printed, "",
556                                 curr * 100 / cmax, curr, cmax);
557                 scr_flush();
558         }
559 }
560
561
562 /*
563  * NOT the same locate_host() in locate_host.c.  This one just does a
564  * 'who am i' to try to discover where the user is...
565  */
566 void locate_host(CtdlIPC* ipc, char *hbuf)
567 {
568         FILE *who = (FILE *)popen("who am i","r");
569         if (who==NULL) {
570                 strcpy(hbuf, ipc->ServInfo.fqdn);
571                 return; 
572         }
573         fgets(hbuf, SIZ, who);
574         pclose(who);
575         stripallbut(hbuf, '(' , ')' );
576 }
577
578 /*
579  * miscellaneous server commands (testing, etc.)
580  */
581 void misc_server_cmd(CtdlIPC *ipc, char *cmd) {
582         char buf[SIZ];
583
584         CtdlIPC_chat_send(ipc, cmd);
585         CtdlIPC_chat_recv(ipc, buf);
586         scr_printf("%s\n",buf);
587         if (buf[0]=='1') {
588                 set_keepalives(KA_HALF);
589                 while (CtdlIPC_chat_recv(ipc, buf), strcmp(buf,"000")) {
590                         scr_printf("%s\n",buf);
591                 }
592                 set_keepalives(KA_YES);
593                 return;
594         }
595         if (buf[0]=='4') {
596                 do {
597                         newprompt("> ",buf,255);
598                         CtdlIPC_chat_send(ipc, buf);
599                 } while(strcmp(buf,"000"));
600                 return;
601         }
602 }
603
604
605 /*
606  * compute the checksum of a file
607  */
608 int file_checksum(char *filename)
609 {
610         int cksum = 0;
611         int ch;
612         FILE *fp;
613
614         fp = fopen(filename,"r");
615         if (fp == NULL) return(0);
616
617         /* yes, this algorithm may allow cksum to overflow, but that's ok
618          * as long as it overflows consistently, which it will.
619          */
620         while (ch=getc(fp), ch>=0) {
621                 cksum = (cksum + ch);
622         }
623
624         fclose(fp);
625         return(cksum);
626 }
627
628 /*
629  * nuke a directory and its contents
630  */
631 int nukedir(char *dirname)
632 {
633         DIR *dp;
634         struct dirent *d;
635         char filename[SIZ];
636
637         dp = opendir(dirname);
638         if (dp == NULL) {
639                 return(errno);
640         }
641
642         while (d = readdir(dp), d != NULL) {
643                 snprintf(filename, sizeof filename, "%s/%s",
644                         dirname, d->d_name);
645                 unlink(filename);
646         }
647
648         closedir(dp);
649         return(rmdir(dirname));
650 }