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