Remove support for the old inline paging mode ... geez, we've only had extended pagin...
[citadel.git] / textclient / citadel_ipc.c
1 // Copyright (c) 1987-2020 by the citadel.org team
2 //
3 // This program is open source software.  Use, duplication, and/or
4 // disclosure are subject to the GNU General Purpose License version 3.
5 //
6 // This program is distributed in the hope that it will be useful,
7 // but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9 // GNU General Public License for more details.
10
11 #include "textclient.h"
12
13 #ifdef HAVE_OPENSSL
14 static SSL_CTX *ssl_ctx;
15 char arg_encrypt;
16 char rc_encrypt;
17
18 #endif                          /* HAVE_OPENSSL */
19
20 #ifndef INADDR_NONE
21 #define INADDR_NONE 0xffffffff
22 #endif
23
24 static void (*status_hook) (char *s) = NULL;
25 char ctdl_autoetc_dir[PATH_MAX] = "";
26 char file_citadel_rc[PATH_MAX] = "";
27 char ctdl_run_dir[PATH_MAX] = "";
28 char ctdl_etc_dir[PATH_MAX] = "";
29 char ctdl_home_directory[PATH_MAX] = "";
30 char file_citadel_socket[PATH_MAX] = "";
31
32 char *viewdefs[] = {
33         "Messages",
34         "Summary",
35         "Address book",
36         "Calendar",
37         "Tasks"
38 };
39
40 char *axdefs[] = {
41         "Deleted",
42         "New User",
43         "Problem User",
44         "Local User",
45         "Normal User",
46         "Preferred User",
47         "Admin",
48         "Admin"
49 };
50
51
52 void CtdlIPC_lock(CtdlIPC * ipc)
53 {
54         if (ipc->network_status_cb)
55                 ipc->network_status_cb(1);
56 }
57
58
59 void CtdlIPC_unlock(CtdlIPC * ipc)
60 {
61         if (ipc->network_status_cb)
62                 ipc->network_status_cb(0);
63 }
64
65
66 char *libcitadelclient_version_string(void)
67 {
68         return "libcitadelclient(unnumbered)";
69 }
70
71
72
73
74 #define COMPUTE_DIRECTORY(SUBDIR) memcpy(dirbuffer,SUBDIR, sizeof dirbuffer);\
75         snprintf(SUBDIR,sizeof SUBDIR,  "%s%s%s%s%s%s%s", \
76                          (home&!relh)?ctdl_home_directory:basedir, \
77              ((basedir!=ctdldir)&(home&!relh))?basedir:"/", \
78              ((basedir!=ctdldir)&(home&!relh))?"/":"", \
79                          relhome, \
80              (relhome[0]!='\0')?"/":"",\
81                          dirbuffer,\
82                          (dirbuffer[0]!='\0')?"/":"");
83
84 #define DBG_PRINT(A) if (dbg==1) fprintf (stderr,"%s : %s \n", #A, A)
85
86
87 void calc_dirs_n_files(int relh, int home, const char *relhome, char *ctdldir, int dbg)
88 {
89         const char *basedir = "";
90         char dirbuffer[PATH_MAX] = "";
91
92         StripSlashes(ctdldir, 1);
93
94 #ifndef HAVE_RUN_DIR
95         basedir = ctdldir;
96 #else
97         basedir = RUN_DIR;
98 #endif
99         COMPUTE_DIRECTORY(ctdl_run_dir);
100         StripSlashes(ctdl_run_dir, 1);
101
102
103 #ifndef HAVE_AUTO_ETC_DIR
104         basedir = ctdldir;
105 #else
106         basedir = AUTO_ETC_DIR;
107 #endif
108         COMPUTE_DIRECTORY(ctdl_autoetc_dir);
109         StripSlashes(ctdl_autoetc_dir, 1);
110
111
112 #ifndef HAVE_ETC_DIR
113         basedir = ctdldir;
114 #else
115         basedir = ETC_DIR;
116 #endif
117         COMPUTE_DIRECTORY(ctdl_etc_dir);
118         StripSlashes(ctdl_etc_dir, 1);
119
120
121
122         snprintf(file_citadel_rc, sizeof file_citadel_rc, "%scitadel.rc", ctdl_etc_dir);
123         StripSlashes(file_citadel_rc, 0);
124
125         snprintf(file_citadel_socket, sizeof file_citadel_socket, "%scitadel.socket", ctdl_run_dir);
126         StripSlashes(file_citadel_socket, 0);
127
128         DBG_PRINT(ctdl_run_dir);
129         DBG_PRINT(file_citadel_socket);
130         DBG_PRINT(ctdl_etc_dir);
131         DBG_PRINT(file_citadel_rc);
132 }
133
134 void setCryptoStatusHook(void (*hook) (char *s))
135 {
136         status_hook = hook;
137 }
138
139 void CtdlIPC_SetNetworkStatusCallback(CtdlIPC * ipc, void (*hook) (int state))
140 {
141         ipc->network_status_cb = hook;
142 }
143
144
145 char instant_msgs = 0;
146
147
148 static void serv_read(CtdlIPC * ipc, char *buf, unsigned int bytes);
149 static void serv_write(CtdlIPC * ipc, const char *buf, unsigned int nbytes);
150 #ifdef HAVE_OPENSSL
151 static void serv_read_ssl(CtdlIPC * ipc, char *buf, unsigned int bytes);
152 static void serv_write_ssl(CtdlIPC * ipc, const char *buf, unsigned int nbytes);
153 static void endtls(SSL * ssl);
154 #endif                          /* HAVE_OPENSSL */
155 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf);
156 static void CtdlIPC_putline(CtdlIPC * ipc, const char *buf);
157
158
159
160 const char *svn_revision(void);
161
162 /*
163  * Does nothing.  The server should always return 200.
164  */
165 int CtdlIPCNoop(CtdlIPC * ipc)
166 {
167         char aaa[128];
168
169         return CtdlIPCGenericCommand(ipc, "NOOP", NULL, 0, NULL, NULL, aaa);
170 }
171
172
173 /*
174  * Does nothing interesting.  The server should always return 200
175  * along with your string.
176  */
177 int CtdlIPCEcho(CtdlIPC * ipc, const char *arg, char *cret)
178 {
179         int ret;
180         char *aaa;
181
182         if (!arg)
183                 return -2;
184         if (!cret)
185                 return -2;
186
187         aaa = (char *) malloc((size_t) (strlen(arg) + 6));
188         if (!aaa)
189                 return -1;
190
191         sprintf(aaa, "ECHO %s", arg);
192         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
193         free(aaa);
194         return ret;
195 }
196
197
198 /*
199  * Asks the server to close the connecction.
200  * Should always return 200.
201  */
202 int CtdlIPCQuit(CtdlIPC * ipc)
203 {
204         int ret = 221;          /* Default to successful quit */
205         char aaa[SIZ];
206
207         CtdlIPC_lock(ipc);
208         if (ipc->sock > -1) {
209                 CtdlIPC_putline(ipc, "QUIT");
210                 CtdlIPC_getline(ipc, aaa);
211                 ret = atoi(aaa);
212         }
213 #ifdef HAVE_OPENSSL
214         if (ipc->ssl)
215                 SSL_shutdown(ipc->ssl);
216         ipc->ssl = NULL;
217 #endif
218         if (ipc->sock)
219                 shutdown(ipc->sock, 2); /* Close connection; we're dead */
220         ipc->sock = -1;
221         CtdlIPC_unlock(ipc);
222         return ret;
223 }
224
225
226 /*
227  * Asks the server to log out.  Should always return 200, even if no user
228  * was logged in.  The user will not be logged in after this!
229  */
230 int CtdlIPCLogout(CtdlIPC * ipc)
231 {
232         int ret;
233         char aaa[SIZ];
234
235         CtdlIPC_lock(ipc);
236         CtdlIPC_putline(ipc, "LOUT");
237         CtdlIPC_getline(ipc, aaa);
238         ret = atoi(aaa);
239         CtdlIPC_unlock(ipc);
240         return ret;
241 }
242
243
244 /*
245  * First stage of authentication - pass the username.  Returns 300 if the
246  * username is able to log in, with the username correctly spelled in cret.
247  * Returns various 500 error codes if the user doesn't exist, etc.
248  */
249 int CtdlIPCTryLogin(CtdlIPC * ipc, const char *username, char *cret)
250 {
251         int ret;
252         char *aaa;
253
254         if (!username)
255                 return -2;
256         if (!cret)
257                 return -2;
258
259         aaa = (char *) malloc((size_t) (strlen(username) + 6));
260         if (!aaa)
261                 return -1;
262
263         sprintf(aaa, "USER %s", username);
264         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
265         free(aaa);
266         return ret;
267 }
268
269
270 /*
271  * Second stage of authentication - provide password.  The server returns
272  * 200 and several arguments in cret relating to the user's account.
273  */
274 int CtdlIPCTryPassword(CtdlIPC * ipc, const char *passwd, char *cret)
275 {
276         int ret;
277         char *aaa;
278
279         if (!passwd)
280                 return -2;
281         if (!cret)
282                 return -2;
283
284         aaa = (char *) malloc((size_t) (strlen(passwd) + 6));
285         if (!aaa)
286                 return -1;
287
288         sprintf(aaa, "PASS %s", passwd);
289         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
290         free(aaa);
291         return ret;
292 }
293
294
295 /*
296  * Second stage of authentication - provide password.  The server returns
297  * 200 and several arguments in cret relating to the user's account.
298  */
299 int CtdlIPCTryApopPassword(CtdlIPC * ipc, const char *response, char *cret)
300 {
301         int ret;
302         char *aaa;
303
304         if (!response)
305                 return -2;
306         if (!cret)
307                 return -2;
308
309         aaa = (char *) malloc((size_t) (strlen(response) + 6));
310         if (!aaa)
311                 return -1;
312
313         sprintf(aaa, "PAS2 %s", response);
314         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
315         free(aaa);
316         return ret;
317 }
318
319
320 /*
321  * Create a new user.  This returns 200 plus the same arguments as TryPassword
322  * if selfservice is nonzero, unless there was a problem creating the account.
323  * If selfservice is zero, creates a new user but does not log out the existing
324  * user - intended for use by system administrators to create accounts on
325  * behalf of other users.
326  */
327 int CtdlIPCCreateUser(CtdlIPC * ipc, const char *username, int selfservice, char *cret)
328 {
329         int ret;
330         char *aaa;
331
332         if (!username)
333                 return -2;
334         if (!cret)
335                 return -2;
336
337         aaa = (char *) malloc((size_t) (strlen(username) + 6));
338         if (!aaa)
339                 return -1;
340
341         sprintf(aaa, "%s %s", selfservice ? "NEWU" : "CREU", username);
342         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
343         free(aaa);
344         return ret;
345 }
346
347
348 /*
349  * Changes the user's password.  Returns 200 if changed, errors otherwise.
350  */
351 int CtdlIPCChangePassword(CtdlIPC * ipc, const char *passwd, char *cret)
352 {
353         int ret;
354         char *aaa;
355
356         if (!passwd)
357                 return -2;
358         if (!cret)
359                 return -2;
360
361         aaa = (char *) malloc((size_t) (strlen(passwd) + 6));
362         if (!aaa)
363                 return -1;
364
365         sprintf(aaa, "SETP %s", passwd);
366         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
367         free(aaa);
368         return ret;
369 }
370
371
372 /* LKRN */
373 /* Caller must free the march list */
374 /* Room types are defined in enum RoomList; keep these in sync! */
375 /* floor is -1 for all, or floornum */
376 int CtdlIPCKnownRooms(CtdlIPC * ipc, enum RoomList which, int floor, struct march **listing, char *cret)
377 {
378         int ret;
379         struct march *march = NULL;
380         static char *proto[] = { "LKRA", "LKRN", "LKRO", "LZRM", "LRMS", "LPRM" };
381         char aaa[SIZ];
382         char *bbb = NULL;
383         size_t bbb_len;
384
385         if (!listing)
386                 return -2;
387         if (*listing)
388                 return -2;      /* Free the listing first */
389         if (!cret)
390                 return -2;
391         /* if (which < 0 || which > 4) return -2; */
392         if (floor < -1)
393                 return -2;      /* Can't validate upper bound, sorry */
394
395         sprintf(aaa, "%s %d", proto[which], floor);
396         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
397         if (ret / 100 == 1) {
398                 struct march *mptr;
399
400                 while (bbb && strlen(bbb)) {
401                         int a;
402
403                         extract_token(aaa, bbb, 0, '\n', sizeof aaa);
404                         a = strlen(aaa);
405                         memmove(bbb, bbb + a + 1, strlen(bbb) - a);
406                         mptr = (struct march *) malloc(sizeof(struct march));
407                         if (mptr) {
408                                 mptr->next = NULL;
409                                 extract_token(mptr->march_name, aaa, 0, '|', sizeof mptr->march_name);
410                                 mptr->march_flags = (unsigned int) extract_int(aaa, 1);
411                                 mptr->march_floor = (char) extract_int(aaa, 2);
412                                 mptr->march_order = (char) extract_int(aaa, 3);
413                                 mptr->march_flags2 = (unsigned int) extract_int(aaa, 4);
414                                 mptr->march_access = (char) extract_int(aaa, 5);
415                                 if (march == NULL)
416                                         march = mptr;
417                                 else {
418                                         struct march *mptr2;
419
420                                         mptr2 = march;
421                                         while (mptr2->next != NULL)
422                                                 mptr2 = mptr2->next;
423                                         mptr2->next = mptr;
424                                 }
425                         }
426                 }
427         }
428         *listing = march;
429         if (bbb)
430                 free(bbb);
431         return ret;
432 }
433
434
435 /* GETU */
436 /* Caller must free the struct ctdluser; caller may pass an existing one */
437 int CtdlIPCGetConfig(CtdlIPC * ipc, struct ctdluser **uret, char *cret)
438 {
439         int ret;
440
441         if (!cret)
442                 return -2;
443         if (!uret)
444                 return -2;
445         if (!*uret)
446                 *uret = (struct ctdluser *) calloc(1, sizeof(struct ctdluser));
447         if (!*uret)
448                 return -1;
449
450         ret = CtdlIPCGenericCommand(ipc, "GETU", NULL, 0, NULL, NULL, cret);
451         if (ret / 100 == 2) {
452                 uret[0]->flags = extract_int(cret, 2);
453         }
454         return ret;
455 }
456
457
458 /* SETU */
459 int CtdlIPCSetConfig(CtdlIPC * ipc, struct ctdluser *uret, char *cret)
460 {
461         char aaa[48];
462
463         if (!uret)
464                 return -2;
465         if (!cret)
466                 return -2;
467
468         sprintf(aaa, "SETU 80|24|%d", uret->flags);
469         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
470 }
471
472
473 /* RENU */
474 int CtdlIPCRenameUser(CtdlIPC * ipc, char *oldname, char *newname, char *cret)
475 {
476         int ret;
477         char cmd[256];
478
479         if (!oldname)
480                 return -2;
481         if (!newname)
482                 return -2;
483         if (!cret)
484                 return -2;
485
486         snprintf(cmd, sizeof cmd, "RENU %s|%s", oldname, newname);
487         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
488         return ret;
489 }
490
491
492 /* GOTO */
493 int CtdlIPCGotoRoom(CtdlIPC * ipc, const char *room, const char *passwd, struct ctdlipcroom **rret, char *cret)
494 {
495         int ret;
496         char *aaa;
497
498         if (!cret)
499                 return -2;
500         if (!rret)
501                 return -2;
502         if (!*rret)
503                 *rret = (struct ctdlipcroom *) calloc(1, sizeof(struct ctdlipcroom));
504         if (!*rret)
505                 return -1;
506
507         if (passwd) {
508                 aaa = (char *) malloc(strlen(room) + strlen(passwd) + 7);
509                 if (!aaa) {
510                         free(*rret);
511                         return -1;
512                 }
513                 sprintf(aaa, "GOTO %s|%s", room, passwd);
514         } else {
515                 aaa = (char *) malloc(strlen(room) + 6);
516                 if (!aaa) {
517                         free(*rret);
518                         return -1;
519                 }
520                 sprintf(aaa, "GOTO %s", room);
521         }
522         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
523         if (ret / 100 == 2) {
524                 extract_token(rret[0]->RRname, cret, 0, '|', sizeof rret[0]->RRname);
525                 rret[0]->RRunread = extract_long(cret, 1);
526                 rret[0]->RRtotal = extract_long(cret, 2);
527                 rret[0]->RRinfoupdated = extract_int(cret, 3);
528                 rret[0]->RRflags = extract_int(cret, 4);
529                 rret[0]->RRhighest = extract_long(cret, 5);
530                 rret[0]->RRlastread = extract_long(cret, 6);
531                 rret[0]->RRismailbox = extract_int(cret, 7);
532                 rret[0]->RRaide = extract_int(cret, 8);
533                 rret[0]->RRnewmail = extract_long(cret, 9);
534                 rret[0]->RRfloor = extract_int(cret, 10);
535                 rret[0]->RRcurrentview = extract_int(cret, 11);
536                 rret[0]->RRdefaultview = extract_int(cret, 12);
537                 /* position 13 is a trash folder flag ... irrelevant in this client */
538                 rret[0]->RRflags2 = extract_int(cret, 14);
539         } else {
540                 free(*rret);
541                 *rret = NULL;
542         }
543         free(aaa);
544         return ret;
545 }
546
547
548 /* MSGS */
549 /* which is 0 = all, 1 = old, 2 = new, 3 = last, 4 = first, 5 = gt, 6 = lt */
550 /* whicharg is number of messages, applies to last, first, gt, lt */
551 int CtdlIPCGetMessages(CtdlIPC * ipc, enum MessageList which, int whicharg, const char *mtemplate, unsigned long **mret, char *cret)
552 {
553         int ret;
554         unsigned long count = 0;
555         static char *proto[] = { "ALL", "OLD", "NEW", "LAST", "FIRST", "GT", "LT" };
556         char aaa[33];
557         char *bbb = NULL;
558         size_t bbb_len;
559
560         if (!cret)
561                 return -2;
562         if (!mret)
563                 return -2;
564         if (*mret)
565                 return -2;
566         if (which < 0 || which > 6)
567                 return -2;
568
569         if (which <= 2)
570                 sprintf(aaa, "MSGS %s||%d", proto[which], (mtemplate) ? 1 : 0);
571         else
572                 sprintf(aaa, "MSGS %s|%d|%d", proto[which], whicharg, (mtemplate) ? 1 : 0);
573         if (mtemplate)
574                 count = strlen(mtemplate);
575         ret = CtdlIPCGenericCommand(ipc, aaa, mtemplate, count, &bbb, &bbb_len, cret);
576         if (ret / 100 != 1)
577                 return ret;
578         count = 0;
579         *mret = (unsigned long *) calloc(1, sizeof(unsigned long));
580         if (!*mret)
581                 return -1;
582         while (bbb && strlen(bbb)) {
583                 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
584                 remove_token(bbb, 0, '\n');
585                 *mret = (unsigned long *) realloc(*mret, (size_t) ((count + 2) * sizeof(unsigned long)));
586                 if (*mret) {
587                         (*mret)[count++] = atol(aaa);
588                         (*mret)[count] = 0L;
589                 } else {
590                         break;
591                 }
592         }
593         if (bbb)
594                 free(bbb);
595         return ret;
596 }
597
598
599 /* MSG0, MSG2 */
600 int CtdlIPCGetSingleMessage(CtdlIPC * ipc, long msgnum, int headers, int as_mime, struct ctdlipcmessage **mret, char *cret)
601 {
602         int ret;
603         char aaa[SIZ];
604         char *bbb = NULL;
605         size_t bbb_len;
606         int multipart_hunting = 0;
607         char multipart_prefix[128];
608         char encoding[256];
609
610         if (!cret)
611                 return -1;
612         if (!mret)
613                 return -1;
614         if (!*mret)
615                 *mret = (struct ctdlipcmessage *) calloc(1, sizeof(struct ctdlipcmessage));
616         if (!*mret)
617                 return -1;
618         if (!msgnum)
619                 return -1;
620
621         strcpy(encoding, "");
622         strcpy(mret[0]->content_type, "");
623         mret[0]->is_local = 0;
624         sprintf(aaa, "MSG%d %ld|%d", as_mime, msgnum, headers);
625         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
626         if (ret / 100 == 1) {
627                 if (as_mime != 2) {
628                         strcpy(mret[0]->mime_chosen, "1");      /* Default chosen-part is "1" */
629                         while (strlen(bbb) > 4 && bbb[4] == '=') {
630                                 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
631                                 remove_token(bbb, 0, '\n');
632
633                                 if (!strncasecmp(aaa, "nhdr=yes", 8))
634                                         mret[0]->nhdr = 1;
635                                 else if (!strncasecmp(aaa, "from=", 5))
636                                         safestrncpy(mret[0]->author, &aaa[5], SIZ);
637                                 else if (!strncasecmp(aaa, "type=", 5))
638                                         mret[0]->type = atoi(&aaa[5]);
639                                 else if (!strncasecmp(aaa, "msgn=", 5))
640                                         safestrncpy(mret[0]->msgid, &aaa[5], SIZ);
641                                 else if (!strncasecmp(aaa, "subj=", 5))
642                                         safestrncpy(mret[0]->subject, &aaa[5], SIZ);
643                                 else if (!strncasecmp(aaa, "rfca=", 5))
644                                         safestrncpy(mret[0]->email, &aaa[5], SIZ);
645                                 else if (!strncasecmp(aaa, "room=", 5))
646                                         safestrncpy(mret[0]->room, &aaa[5], SIZ);
647                                 else if (!strncasecmp(aaa, "rcpt=", 5))
648                                         safestrncpy(mret[0]->recipient, &aaa[5], SIZ);
649                                 else if (!strncasecmp(aaa, "wefw=", 5))
650                                         safestrncpy(mret[0]->references, &aaa[5], SIZ);
651                                 else if (!strncasecmp(aaa, "time=", 5))
652                                         mret[0]->time = atol(&aaa[5]);
653                                 else if (!strncasecmp(aaa, "locl", 4))
654                                         mret[0]->is_local = 1;
655
656                                 /* Multipart/alternative prefix & suffix strings help
657                                  * us to determine which part we want to download.
658                                  */
659                                 else if (!strncasecmp(aaa, "pref=", 5)) {
660                                         extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
661                                         if (!strcasecmp(multipart_prefix, "multipart/alternative")) {
662                                                 ++multipart_hunting;
663                                         }
664                                 } else if (!strncasecmp(aaa, "suff=", 5)) {
665                                         extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
666                                         if (!strcasecmp(multipart_prefix, "multipart/alternative")) {
667                                                 ++multipart_hunting;
668                                         }
669                                 }
670
671                                 else if (!strncasecmp(aaa, "part=", 5)) {
672                                         struct parts *ptr, *chain;
673
674                                         ptr = (struct parts *) calloc(1, sizeof(struct parts));
675                                         if (ptr) {
676
677                                                 /* Fill the buffers for the caller */
678                                                 extract_token(ptr->name, &aaa[5], 0, '|', sizeof ptr->name);
679                                                 extract_token(ptr->filename, &aaa[5], 1, '|', sizeof ptr->filename);
680                                                 extract_token(ptr->number, &aaa[5], 2, '|', sizeof ptr->number);
681                                                 extract_token(ptr->disposition, &aaa[5], 3, '|', sizeof ptr->disposition);
682                                                 extract_token(ptr->mimetype, &aaa[5], 4, '|', sizeof ptr->mimetype);
683                                                 ptr->length = extract_long(&aaa[5], 5);
684                                                 if (!mret[0]->attachments)
685                                                         mret[0]->attachments = ptr;
686                                                 else {
687                                                         chain = mret[0]->attachments;
688                                                         while (chain->next)
689                                                                 chain = chain->next;
690                                                         chain->next = ptr;
691                                                 }
692
693                                                 /* Now handle multipart/alternative */
694                                                 if (multipart_hunting > 0) {
695                                                         if ((!strcasecmp(ptr->mimetype, "text/plain"))
696                                                             || (!strcasecmp(ptr->mimetype, "text/html"))) {
697                                                                 strcpy(mret[0]->mime_chosen, ptr->number);
698                                                         }
699                                                 }
700
701                                         }
702                                 }
703                         }
704                         /* Eliminate "text\n" */
705                         remove_token(bbb, 0, '\n');
706
707                         /* If doing a MIME thing, pull out the extra headers */
708                         if (as_mime == 4) {
709                                 do {
710                                         if (!strncasecmp(bbb, "Content-type:", 13)) {
711                                                 extract_token(mret[0]->content_type, bbb, 0, '\n', sizeof mret[0]->content_type);
712                                                 strcpy(mret[0]->content_type, &mret[0]->content_type[13]);
713                                                 striplt(mret[0]->content_type);
714
715                                                 /* strip out ";charset=" portion.  FIXME do something with
716                                                  * the charset (like... convert it) instead of just throwing
717                                                  * it away
718                                                  */
719                                                 if (strstr(mret[0]->content_type, ";") != NULL) {
720                                                         strcpy(strstr(mret[0]->content_type, ";"), "");
721                                                 }
722
723                                         }
724                                         if (!strncasecmp(bbb, "X-Citadel-MSG4-Partnum:", 23)) {
725                                                 extract_token(mret[0]->mime_chosen, bbb, 0, '\n', sizeof mret[0]->mime_chosen);
726                                                 strcpy(mret[0]->mime_chosen, &mret[0]->mime_chosen[23]);
727                                                 striplt(mret[0]->mime_chosen);
728                                         }
729                                         if (!strncasecmp(bbb, "Content-transfer-encoding:", 26)) {
730                                                 extract_token(encoding, bbb, 0, '\n', sizeof encoding);
731                                                 strcpy(encoding, &encoding[26]);
732                                                 striplt(encoding);
733                                         }
734                                         remove_token(bbb, 0, '\n');
735                                 } while ((bbb[0] != 0) && (bbb[0] != '\n'));
736                                 remove_token(bbb, 0, '\n');
737                         }
738
739
740                 }
741                 if (strlen(bbb)) {
742
743                         if ((!strcasecmp(encoding, "base64")) || (!strcasecmp(encoding, "quoted-printable"))) {
744                                 char *ccc = NULL;
745                                 int bytes_decoded = 0;
746                                 ccc = malloc(strlen(bbb) + 32768);
747                                 if (!strcasecmp(encoding, "base64")) {
748                                         bytes_decoded = CtdlDecodeBase64(ccc, bbb, strlen(bbb));
749                                 } else if (!strcasecmp(encoding, "quoted-printable")) {
750                                         bytes_decoded = CtdlDecodeQuotedPrintable(ccc, bbb, strlen(bbb));
751                                 }
752                                 ccc[bytes_decoded] = 0;
753                                 free(bbb);
754                                 bbb = ccc;
755                         }
756
757                         /* FIXME: Strip trailing whitespace */
758                         bbb = (char *) realloc(bbb, (size_t) (strlen(bbb) + 1));
759
760                 } else {
761                         bbb = (char *) realloc(bbb, 1);
762                         *bbb = '\0';
763                 }
764                 mret[0]->text = bbb;
765         }
766         return ret;
767 }
768
769
770 /* WHOK */
771 int CtdlIPCWhoKnowsRoom(CtdlIPC * ipc, char **listing, char *cret)
772 {
773         int ret;
774         size_t bytes;
775
776         if (!cret)
777                 return -2;
778         if (!listing)
779                 return -2;
780         if (*listing)
781                 return -2;
782
783         ret = CtdlIPCGenericCommand(ipc, "WHOK", NULL, 0, listing, &bytes, cret);
784         return ret;
785 }
786
787
788 /* INFO */
789 int CtdlIPCServerInfo(CtdlIPC * ipc, char *cret)
790 {
791         int ret;
792         size_t bytes;
793         char *listing = NULL;
794         char buf[SIZ];
795
796         if (!cret)
797                 return -2;
798
799         ret = CtdlIPCGenericCommand(ipc, "INFO", NULL, 0, &listing, &bytes, cret);
800         if (ret / 100 == 1) {
801                 int line = 0;
802
803                 while (*listing && strlen(listing)) {
804                         extract_token(buf, listing, 0, '\n', sizeof buf);
805                         remove_token(listing, 0, '\n');
806                         switch (line++) {
807                         case 0:
808                                 ipc->ServInfo.pid = atoi(buf);
809                                 break;
810                         case 1:
811                                 strcpy(ipc->ServInfo.nodename, buf);
812                                 break;
813                         case 2:
814                                 strcpy(ipc->ServInfo.humannode, buf);
815                                 break;
816                         case 3:
817                                 strcpy(ipc->ServInfo.fqdn, buf);
818                                 break;
819                         case 4:
820                                 strcpy(ipc->ServInfo.software, buf);
821                                 break;
822                         case 5:
823                                 ipc->ServInfo.rev_level = atoi(buf);
824                                 break;
825                         case 6:
826                                 strcpy(ipc->ServInfo.site_location, buf);
827                                 break;
828                         case 7:
829                                 strcpy(ipc->ServInfo.sysadm, buf);
830                                 break;
831                         case 9:
832                                 strcpy(ipc->ServInfo.moreprompt, buf);
833                                 break;
834                         case 10:
835                                 ipc->ServInfo.ok_floors = atoi(buf);
836                                 break;
837                         case 13:
838                                 ipc->ServInfo.supports_qnop = atoi(buf);
839                                 break;
840                         case 14:
841                                 ipc->ServInfo.supports_ldap = atoi(buf);
842                                 break;
843                         case 15:
844                                 ipc->ServInfo.newuser_disabled = atoi(buf);
845                                 break;
846                         case 16:
847                                 strcpy(ipc->ServInfo.default_cal_zone, buf);
848                                 break;
849                         case 17:
850                                 ipc->ServInfo.load_avg = atof(buf);
851                                 break;
852                         case 18:
853                                 ipc->ServInfo.worker_avg = atof(buf);
854                                 break;
855                         case 19:
856                                 ipc->ServInfo.thread_count = atoi(buf);
857                                 break;
858                         case 20:
859                                 ipc->ServInfo.has_sieve = atoi(buf);
860                                 break;
861                         case 21:
862                                 ipc->ServInfo.fulltext_enabled = atoi(buf);
863                                 break;
864                         case 22:
865                                 strcpy(ipc->ServInfo.svn_revision, buf);
866                                 break;
867                         case 24:
868                                 ipc->ServInfo.guest_logins = atoi(buf);
869                                 break;
870                         }
871                 }
872
873         }
874         if (listing)
875                 free(listing);
876         return ret;
877 }
878
879
880 /* RDIR */
881 int CtdlIPCReadDirectory(CtdlIPC * ipc, char **listing, char *cret)
882 {
883         int ret;
884         size_t bytes;
885
886         if (!cret)
887                 return -2;
888         if (!listing)
889                 return -2;
890         if (*listing)
891                 return -2;
892
893         ret = CtdlIPCGenericCommand(ipc, "RDIR", NULL, 0, listing, &bytes, cret);
894         return ret;
895 }
896
897
898 /*
899  * Set last-read pointer in this room to msgnum, or 0 for HIGHEST.
900  */
901 int CtdlIPCSetLastRead(CtdlIPC * ipc, long msgnum, char *cret)
902 {
903         int ret;
904         char aaa[64];
905
906         if (!cret)
907                 return -2;
908
909         if (msgnum) {
910                 sprintf(aaa, "SLRP %ld", msgnum);
911         } else {
912                 sprintf(aaa, "SLRP HIGHEST");
913         }
914         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
915         return ret;
916 }
917
918
919 /* INVT */
920 int CtdlIPCInviteUserToRoom(CtdlIPC * ipc, const char *username, char *cret)
921 {
922         int ret;
923         char *aaa;
924
925         if (!cret)
926                 return -2;
927         if (!username)
928                 return -2;
929
930         aaa = (char *) malloc(strlen(username) + 6);
931         if (!aaa)
932                 return -1;
933
934         sprintf(aaa, "INVT %s", username);
935         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
936         free(aaa);
937         return ret;
938 }
939
940
941 /* KICK */
942 int CtdlIPCKickoutUserFromRoom(CtdlIPC * ipc, const char *username, char *cret)
943 {
944         int ret;
945         char *aaa;
946
947         if (!cret)
948                 return -1;
949         if (!username)
950                 return -1;
951
952         aaa = (char *) malloc(strlen(username) + 6);
953
954         sprintf(aaa, "KICK %s", username);
955         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
956         free(aaa);
957         return ret;
958 }
959
960
961 /* GETR */
962 int CtdlIPCGetRoomAttributes(CtdlIPC * ipc, struct ctdlroom **qret, char *cret)
963 {
964         int ret;
965
966         if (!cret)
967                 return -2;
968         if (!qret)
969                 return -2;
970         if (!*qret)
971                 *qret = (struct ctdlroom *) calloc(1, sizeof(struct ctdlroom));
972         if (!*qret)
973                 return -1;
974
975         ret = CtdlIPCGenericCommand(ipc, "GETR", NULL, 0, NULL, NULL, cret);
976         if (ret / 100 == 2) {
977                 extract_token(qret[0]->QRname, cret, 0, '|', sizeof qret[0]->QRname);
978                 extract_token(qret[0]->QRpasswd, cret, 1, '|', sizeof qret[0]->QRpasswd);
979                 extract_token(qret[0]->QRdirname, cret, 2, '|', sizeof qret[0]->QRdirname);
980                 qret[0]->QRflags = extract_int(cret, 3);
981                 qret[0]->QRfloor = extract_int(cret, 4);
982                 qret[0]->QRorder = extract_int(cret, 5);
983                 qret[0]->QRdefaultview = extract_int(cret, 6);
984                 qret[0]->QRflags2 = extract_int(cret, 7);
985         }
986         return ret;
987 }
988
989
990 /* SETR */
991 /* set forget to kick all users out of room */
992 int CtdlIPCSetRoomAttributes(CtdlIPC * ipc, int forget, struct ctdlroom *qret, char *cret)
993 {
994         int ret;
995         char *aaa;
996
997         if (!cret)
998                 return -2;
999         if (!qret)
1000                 return -2;
1001
1002         aaa = (char *) malloc(strlen(qret->QRname) + strlen(qret->QRpasswd) + strlen(qret->QRdirname) + 64);
1003         if (!aaa)
1004                 return -1;
1005
1006         sprintf(aaa, "SETR %s|%s|%s|%d|%d|%d|%d|%d|%d",
1007                 qret->QRname, qret->QRpasswd, qret->QRdirname,
1008                 qret->QRflags, forget, qret->QRfloor, qret->QRorder, qret->QRdefaultview, qret->QRflags2);
1009         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1010         free(aaa);
1011         return ret;
1012 }
1013
1014
1015 /* GETA */
1016 int CtdlIPCGetRoomAide(CtdlIPC * ipc, char *cret)
1017 {
1018         if (!cret)
1019                 return -1;
1020
1021         return CtdlIPCGenericCommand(ipc, "GETA", NULL, 0, NULL, NULL, cret);
1022 }
1023
1024
1025 /* SETA */
1026 int CtdlIPCSetRoomAide(CtdlIPC * ipc, const char *username, char *cret)
1027 {
1028         int ret;
1029         char *aaa;
1030
1031         if (!cret)
1032                 return -2;
1033         if (!username)
1034                 return -2;
1035
1036         aaa = (char *) malloc(strlen(username) + 6);
1037         if (!aaa)
1038                 return -1;
1039
1040         sprintf(aaa, "SETA %s", username);
1041         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1042         free(aaa);
1043         return ret;
1044 }
1045
1046
1047 /* ENT0 */
1048 int CtdlIPCPostMessage(CtdlIPC * ipc, int flag, int *subject_required, struct ctdlipcmessage *mr, char *cret)
1049 {
1050         int ret;
1051         char cmd[SIZ];
1052         char *ptr;
1053
1054         if (!cret)
1055                 return -2;
1056         if (!mr)
1057                 return -2;
1058
1059         if (mr->references) {
1060                 for (ptr = mr->references; *ptr != 0; ++ptr) {
1061                         if (*ptr == '|')
1062                                 *ptr = '!';
1063                 }
1064         }
1065
1066         snprintf(cmd, sizeof cmd,
1067                  "ENT0 %d|%s|%d|%d|%s|%s||||||%s|", flag, mr->recipient,
1068                  mr->anonymous, mr->type, mr->subject, mr->author, mr->references);
1069         ret = CtdlIPCGenericCommand(ipc, cmd, mr->text, strlen(mr->text), NULL, NULL, cret);
1070         if ((flag == 0) && (subject_required != NULL)) {
1071                 /* Is the server strongly recommending that the user enter a message subject? */
1072                 if ((cret[3] != '\0') && (cret[4] != '\0')) {
1073                         *subject_required = extract_int(&cret[4], 1);
1074                 }
1075
1076
1077         }
1078         return ret;
1079 }
1080
1081
1082 /* RINF */
1083 int CtdlIPCRoomInfo(CtdlIPC * ipc, char **iret, char *cret)
1084 {
1085         size_t bytes;
1086
1087         if (!cret)
1088                 return -2;
1089         if (!iret)
1090                 return -2;
1091         if (*iret)
1092                 return -2;
1093
1094         return CtdlIPCGenericCommand(ipc, "RINF", NULL, 0, iret, &bytes, cret);
1095 }
1096
1097
1098 /* DELE */
1099 int CtdlIPCDeleteMessage(CtdlIPC * ipc, long msgnum, char *cret)
1100 {
1101         char aaa[64];
1102
1103         if (!cret)
1104                 return -2;
1105         if (!msgnum)
1106                 return -2;
1107
1108         sprintf(aaa, "DELE %ld", msgnum);
1109         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1110 }
1111
1112
1113 /* MOVE */
1114 int CtdlIPCMoveMessage(CtdlIPC * ipc, int copy, long msgnum, const char *destroom, char *cret)
1115 {
1116         int ret;
1117         char *aaa;
1118
1119         if (!cret)
1120                 return -2;
1121         if (!destroom)
1122                 return -2;
1123         if (!msgnum)
1124                 return -2;
1125
1126         aaa = (char *) malloc(strlen(destroom) + 28);
1127         if (!aaa)
1128                 return -1;
1129
1130         sprintf(aaa, "MOVE %ld|%s|%d", msgnum, destroom, copy);
1131         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1132         free(aaa);
1133         return ret;
1134 }
1135
1136
1137 /* KILL */
1138 int CtdlIPCDeleteRoom(CtdlIPC * ipc, int for_real, char *cret)
1139 {
1140         char aaa[64];
1141
1142         if (!cret)
1143                 return -2;
1144
1145         sprintf(aaa, "KILL %d", for_real);
1146         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1147 }
1148
1149
1150 /* CRE8 */
1151 int CtdlIPCCreateRoom(CtdlIPC * ipc, int for_real, const char *roomname, int type, const char *password, int floor, char *cret)
1152 {
1153         int ret;
1154         char *aaa;
1155
1156         if (!cret)
1157                 return -2;
1158         if (!roomname)
1159                 return -2;
1160
1161         if (password) {
1162                 aaa = (char *) malloc(strlen(roomname) + strlen(password) + 40);
1163                 if (!aaa)
1164                         return -1;
1165                 sprintf(aaa, "CRE8 %d|%s|%d|%s|%d", for_real, roomname, type, password, floor);
1166         } else {
1167                 aaa = (char *) malloc(strlen(roomname) + 40);
1168                 if (!aaa)
1169                         return -1;
1170                 sprintf(aaa, "CRE8 %d|%s|%d||%d", for_real, roomname, type, floor);
1171         }
1172         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1173         free(aaa);
1174         return ret;
1175 }
1176
1177
1178 /* FORG */
1179 int CtdlIPCForgetRoom(CtdlIPC * ipc, char *cret)
1180 {
1181         if (!cret)
1182                 return -2;
1183
1184         return CtdlIPCGenericCommand(ipc, "FORG", NULL, 0, NULL, NULL, cret);
1185 }
1186
1187
1188 /* MESG */
1189 int CtdlIPCSystemMessage(CtdlIPC * ipc, const char *message, char **mret, char *cret)
1190 {
1191         int ret;
1192         char *aaa;
1193         size_t bytes;
1194
1195         if (!cret)
1196                 return -2;
1197         if (!mret)
1198                 return -2;
1199         if (*mret)
1200                 return -2;
1201         if (!message)
1202                 return -2;
1203
1204         aaa = (char *) malloc(strlen(message) + 6);
1205         if (!aaa)
1206                 return -1;
1207
1208         sprintf(aaa, "MESG %s", message);
1209         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, mret, &bytes, cret);
1210         free(aaa);
1211         return ret;
1212 }
1213
1214
1215 /* GNUR */
1216 int CtdlIPCNextUnvalidatedUser(CtdlIPC * ipc, char *cret)
1217 {
1218         if (!cret)
1219                 return -2;
1220
1221         return CtdlIPCGenericCommand(ipc, "GNUR", NULL, 0, NULL, NULL, cret);
1222 }
1223
1224
1225 /* GREG */
1226 int CtdlIPCGetUserRegistration(CtdlIPC * ipc, const char *username, char **rret, char *cret)
1227 {
1228         int ret;
1229         char *aaa;
1230         size_t bytes;
1231
1232         if (!cret)
1233                 return -2;
1234         if (!rret)
1235                 return -2;
1236         if (*rret)
1237                 return -2;
1238
1239         if (username)
1240                 aaa = (char *) malloc(strlen(username) + 6);
1241         else
1242                 aaa = (char *) malloc(12);
1243         if (!aaa)
1244                 return -1;
1245
1246         if (username)
1247                 sprintf(aaa, "GREG %s", username);
1248         else
1249                 sprintf(aaa, "GREG _SELF_");
1250         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, rret, &bytes, cret);
1251         free(aaa);
1252         return ret;
1253 }
1254
1255
1256 /* VALI */
1257 int CtdlIPCValidateUser(CtdlIPC * ipc, const char *username, int axlevel, char *cret)
1258 {
1259         int ret;
1260         char *aaa;
1261
1262         if (!cret)
1263                 return -2;
1264         if (!username)
1265                 return -2;
1266         if (axlevel < AxDeleted || axlevel > AxAideU)
1267                 return -2;
1268
1269         aaa = (char *) malloc(strlen(username) + 17);
1270         if (!aaa)
1271                 return -1;
1272
1273         sprintf(aaa, "VALI %s|%d", username, axlevel);
1274         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1275         free(aaa);
1276         return ret;
1277 }
1278
1279
1280 /* EINF */
1281 int CtdlIPCSetRoomInfo(CtdlIPC * ipc, int for_real, const char *info, char *cret)
1282 {
1283         char aaa[64];
1284
1285         if (!cret)
1286                 return -1;
1287         if (!info)
1288                 return -1;
1289
1290         sprintf(aaa, "EINF %d", for_real);
1291         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1292 }
1293
1294
1295 /* LIST */
1296 int CtdlIPCUserListing(CtdlIPC * ipc, char *searchstring, char **listing, char *cret)
1297 {
1298         size_t bytes;
1299         char *cmd;
1300         int ret;
1301
1302         if (!cret)
1303                 return -1;
1304         if (!listing)
1305                 return -1;
1306         if (*listing)
1307                 return -1;
1308         if (!searchstring)
1309                 return -1;
1310
1311         cmd = malloc(strlen(searchstring) + 10);
1312         sprintf(cmd, "LIST %s", searchstring);
1313
1314         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, listing, &bytes, cret);
1315         free(cmd);
1316         return (ret);
1317 }
1318
1319
1320 /* REGI */
1321 int CtdlIPCSetRegistration(CtdlIPC * ipc, const char *info, char *cret)
1322 {
1323         if (!cret)
1324                 return -1;
1325         if (!info)
1326                 return -1;
1327
1328         return CtdlIPCGenericCommand(ipc, "REGI", info, strlen(info), NULL, NULL, cret);
1329 }
1330
1331
1332 /* CHEK */
1333 int CtdlIPCMiscCheck(CtdlIPC * ipc, struct ctdlipcmisc *chek, char *cret)
1334 {
1335         int ret;
1336
1337         if (!cret)
1338                 return -1;
1339         if (!chek)
1340                 return -1;
1341
1342         ret = CtdlIPCGenericCommand(ipc, "CHEK", NULL, 0, NULL, NULL, cret);
1343         if (ret / 100 == 2) {
1344                 chek->newmail = extract_long(cret, 0);
1345                 chek->needregis = extract_int(cret, 1);
1346                 chek->needvalid = extract_int(cret, 2);
1347         }
1348         return ret;
1349 }
1350
1351
1352 /* DELF */
1353 int CtdlIPCDeleteFile(CtdlIPC * ipc, const char *filename, char *cret)
1354 {
1355         int ret;
1356         char *aaa;
1357
1358         if (!cret)
1359                 return -2;
1360         if (!filename)
1361                 return -2;
1362
1363         aaa = (char *) malloc(strlen(filename) + 6);
1364         if (!aaa)
1365                 return -1;
1366
1367         sprintf(aaa, "DELF %s", filename);
1368         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1369         free(aaa);
1370         return ret;
1371 }
1372
1373
1374 /* MOVF */
1375 int CtdlIPCMoveFile(CtdlIPC * ipc, const char *filename, const char *destroom, char *cret)
1376 {
1377         int ret;
1378         char *aaa;
1379
1380         if (!cret)
1381                 return -2;
1382         if (!filename)
1383                 return -2;
1384         if (!destroom)
1385                 return -2;
1386
1387         aaa = (char *) malloc(strlen(filename) + strlen(destroom) + 7);
1388         if (!aaa)
1389                 return -1;
1390
1391         sprintf(aaa, "MOVF %s|%s", filename, destroom);
1392         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1393         free(aaa);
1394         return ret;
1395 }
1396
1397
1398 /* RWHO */
1399 int CtdlIPCOnlineUsers(CtdlIPC * ipc, char **listing, time_t * stamp, char *cret)
1400 {
1401         int ret;
1402         size_t bytes;
1403
1404         if (!cret)
1405                 return -1;
1406         if (!listing)
1407                 return -1;
1408         if (*listing)
1409                 return -1;
1410
1411         *stamp = CtdlIPCServerTime(ipc, cret);
1412         if (!*stamp)
1413                 *stamp = time(NULL);
1414         ret = CtdlIPCGenericCommand(ipc, "RWHO", NULL, 0, listing, &bytes, cret);
1415         return ret;
1416 }
1417
1418
1419 /* OPEN */
1420 int CtdlIPCFileDownload(CtdlIPC * ipc, const char *filename, void **buf, size_t resume, void (*progress_gauge_callback)
1421                          (CtdlIPC *, unsigned long, unsigned long), char *cret)
1422 {
1423         int ret;
1424         size_t bytes;
1425         time_t last_mod;
1426         char mimetype[SIZ];
1427         char *aaa;
1428
1429         if (!cret)
1430                 return -2;
1431         if (!filename)
1432                 return -2;
1433         if (!buf)
1434                 return -2;
1435         if (*buf)
1436                 return -2;
1437         if (ipc->downloading)
1438                 return -2;
1439
1440         aaa = (char *) malloc(strlen(filename) + 6);
1441         if (!aaa)
1442                 return -1;
1443
1444         sprintf(aaa, "OPEN %s", filename);
1445         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1446         free(aaa);
1447         if (ret / 100 == 2) {
1448                 ipc->downloading = 1;
1449                 bytes = extract_long(cret, 0);
1450                 last_mod = extract_int(cret, 1);
1451                 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1452
1453                 ret = CtdlIPCReadDownload(ipc, buf, bytes, resume, progress_gauge_callback, cret);
1454                 /*
1455                    ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, resume,
1456                    progress_gauge_callback, cret);
1457                  */
1458
1459                 ret = CtdlIPCEndDownload(ipc, cret);
1460                 if (ret / 100 == 2)
1461                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1462         }
1463         return ret;
1464 }
1465
1466
1467 /* OPNA */
1468 int CtdlIPCAttachmentDownload(CtdlIPC * ipc, long msgnum, const char *part, void **buf, void (*progress_gauge_callback)
1469                                (CtdlIPC *, unsigned long, unsigned long), char *cret)
1470 {
1471         int ret;
1472         size_t bytes;
1473         time_t last_mod;
1474         char filename[SIZ];
1475         char mimetype[SIZ];
1476         char aaa[SIZ];
1477
1478         if (!cret)
1479                 return -2;
1480         if (!buf)
1481                 return -2;
1482         if (*buf)
1483                 return -2;
1484         if (!part)
1485                 return -2;
1486         if (!msgnum)
1487                 return -2;
1488         if (ipc->downloading)
1489                 return -2;
1490
1491         sprintf(aaa, "OPNA %ld|%s", msgnum, part);
1492         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1493         if (ret / 100 == 2) {
1494                 ipc->downloading = 1;
1495                 bytes = extract_long(cret, 0);
1496                 last_mod = extract_int(cret, 1);
1497                 extract_token(filename, cret, 2, '|', sizeof filename);
1498                 extract_token(mimetype, cret, 3, '|', sizeof mimetype);
1499                 /* ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1500                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1501                 ret = CtdlIPCEndDownload(ipc, cret);
1502                 if (ret / 100 == 2)
1503                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1504         }
1505         return ret;
1506 }
1507
1508
1509 /* OIMG */
1510 int CtdlIPCImageDownload(CtdlIPC * ipc, const char *filename, void **buf, void (*progress_gauge_callback)
1511                           (CtdlIPC *, unsigned long, unsigned long), char *cret)
1512 {
1513         int ret;
1514         size_t bytes;
1515         time_t last_mod;
1516         char mimetype[SIZ];
1517         char *aaa;
1518
1519         if (!cret)
1520                 return -1;
1521         if (!buf)
1522                 return -1;
1523         if (*buf)
1524                 return -1;
1525         if (!filename)
1526                 return -1;
1527         if (ipc->downloading)
1528                 return -1;
1529
1530         aaa = (char *) malloc(strlen(filename) + 6);
1531         if (!aaa)
1532                 return -1;
1533
1534         sprintf(aaa, "OIMG %s", filename);
1535         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1536         free(aaa);
1537         if (ret / 100 == 2) {
1538                 ipc->downloading = 1;
1539                 bytes = extract_long(cret, 0);
1540                 last_mod = extract_int(cret, 1);
1541                 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1542 /*              ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1543                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1544                 ret = CtdlIPCEndDownload(ipc, cret);
1545                 if (ret / 100 == 2)
1546                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1547         }
1548         return ret;
1549 }
1550
1551
1552 /* UOPN */
1553 int CtdlIPCFileUpload(CtdlIPC * ipc, const char *save_as, const char *comment, const char *path, void (*progress_gauge_callback)
1554                        (CtdlIPC *, unsigned long, unsigned long), char *cret)
1555 {
1556         int ret;
1557         char *aaa;
1558         FILE *uploadFP;
1559         char MimeTestBuf[64];
1560         const char *MimeType;
1561         long len;
1562
1563         if (!cret)
1564                 return -1;
1565         if (!save_as)
1566                 return -1;
1567         if (!comment)
1568                 return -1;
1569         if (!path)
1570                 return -1;
1571         if (!*path)
1572                 return -1;
1573         if (ipc->uploading)
1574                 return -1;
1575
1576         uploadFP = fopen(path, "r");
1577         if (!uploadFP)
1578                 return -2;
1579
1580         len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1581         rewind(uploadFP);
1582         if (len < 0)
1583                 return -3;
1584
1585         MimeType = GuessMimeType(&MimeTestBuf[0], len);
1586         aaa = (char *) malloc(strlen(save_as) + strlen(MimeType) + strlen(comment) + 7);
1587         if (!aaa)
1588                 return -1;
1589
1590         sprintf(aaa, "UOPN %s|%s|%s", save_as, MimeType, comment);
1591         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1592         free(aaa);
1593         if (ret / 100 == 2) {
1594                 ipc->uploading = 1;
1595                 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1596                 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1597                 ipc->uploading = 0;
1598         }
1599         return ret;
1600 }
1601
1602
1603 /* UIMG */
1604 int CtdlIPCImageUpload(CtdlIPC * ipc, int for_real, const char *path, const char *save_as, void (*progress_gauge_callback)
1605                         (CtdlIPC *, unsigned long, unsigned long), char *cret)
1606 {
1607         int ret;
1608         FILE *uploadFP;
1609         char *aaa;
1610         char MimeTestBuf[64];
1611         const char *MimeType;
1612         long len;
1613
1614         if (!cret)
1615                 return -1;
1616         if (!save_as)
1617                 return -1;
1618         if (!path && for_real)
1619                 return -1;
1620         if (!*path && for_real)
1621                 return -1;
1622         if (ipc->uploading)
1623                 return -1;
1624
1625         aaa = (char *) malloc(strlen(save_as) + 17);
1626         if (!aaa)
1627                 return -1;
1628
1629         uploadFP = fopen(path, "r");
1630         if (!uploadFP)
1631                 return -2;
1632
1633         len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1634         rewind(uploadFP);
1635         if (len < 0)
1636                 return -3;
1637         MimeType = GuessMimeType(&MimeTestBuf[0], 64);
1638
1639         sprintf(aaa, "UIMG %d|%s|%s", for_real, MimeType, save_as);
1640         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1641         free(aaa);
1642         if (ret / 100 == 2 && for_real) {
1643                 ipc->uploading = 1;
1644                 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1645                 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1646                 ipc->uploading = 0;
1647         }
1648         return ret;
1649 }
1650
1651
1652 /* QUSR */
1653 int CtdlIPCQueryUsername(CtdlIPC * ipc, const char *username, char *cret)
1654 {
1655         int ret;
1656         char *aaa;
1657
1658         if (!cret)
1659                 return -2;
1660         if (!username)
1661                 return -2;
1662
1663         aaa = (char *) malloc(strlen(username) + 6);
1664         if (!aaa)
1665                 return -1;
1666
1667         sprintf(aaa, "QUSR %s", username);
1668         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1669         free(aaa);
1670         return ret;
1671 }
1672
1673
1674 /* LFLR */
1675 int CtdlIPCFloorListing(CtdlIPC * ipc, char **listing, char *cret)
1676 {
1677         size_t bytes;
1678
1679         if (!cret)
1680                 return -2;
1681         if (!listing)
1682                 return -2;
1683         if (*listing)
1684                 return -2;
1685
1686         return CtdlIPCGenericCommand(ipc, "LFLR", NULL, 0, listing, &bytes, cret);
1687 }
1688
1689
1690 /* CFLR */
1691 int CtdlIPCCreateFloor(CtdlIPC * ipc, int for_real, const char *name, char *cret)
1692 {
1693         int ret;
1694         char aaa[SIZ];
1695
1696         if (!cret)
1697                 return -2;
1698         if (!name)
1699                 return -2;
1700
1701         sprintf(aaa, "CFLR %s|%d", name, for_real);
1702         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1703         return ret;
1704 }
1705
1706
1707 /* KFLR */
1708 int CtdlIPCDeleteFloor(CtdlIPC * ipc, int for_real, int floornum, char *cret)
1709 {
1710         char aaa[SIZ];
1711
1712         if (!cret)
1713                 return -1;
1714         if (floornum < 0)
1715                 return -1;
1716
1717         sprintf(aaa, "KFLR %d|%d", floornum, for_real);
1718         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1719 }
1720
1721
1722 /* EFLR */
1723 int CtdlIPCEditFloor(CtdlIPC * ipc, int floornum, const char *floorname, char *cret)
1724 {
1725         int ret;
1726         char aaa[SIZ];
1727
1728         if (!cret)
1729                 return -2;
1730         if (!floorname)
1731                 return -2;
1732         if (floornum < 0)
1733                 return -2;
1734
1735         sprintf(aaa, "EFLR %d|%s", floornum, floorname);
1736         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1737         return ret;
1738 }
1739
1740
1741 /*
1742  * IDEN 
1743  *
1744  * You only need to fill out hostname, the defaults will be used if any of the
1745  * other fields are not set properly.
1746  */
1747 int CtdlIPCIdentifySoftware(CtdlIPC * ipc, int developerid, int clientid,
1748                             int revision, const char *software_name, const char *hostname, char *cret)
1749 {
1750         int ret;
1751         char *aaa;
1752
1753         if (developerid < 0 || clientid < 0 || revision < 0 || !software_name) {
1754                 developerid = 8;
1755                 clientid = 0;
1756                 revision = CLIENT_VERSION - 600;
1757                 software_name = "Citadel (libcitadel)";
1758         }
1759         if (!hostname)
1760                 return -2;
1761
1762         aaa = (char *) malloc(strlen(software_name) + strlen(hostname) + 29);
1763         if (!aaa)
1764                 return -1;
1765
1766         sprintf(aaa, "IDEN %d|%d|%d|%s|%s", developerid, clientid, revision, software_name, hostname);
1767         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1768         free(aaa);
1769         return ret;
1770 }
1771
1772
1773 /* SEXP */
1774 int CtdlIPCSendInstantMessage(CtdlIPC * ipc, const char *username, const char *text, char *cret)
1775 {
1776         int ret;
1777         char *aaa;
1778
1779         if (!cret)
1780                 return -2;
1781         if (!username)
1782                 return -2;
1783
1784         aaa = (char *) malloc(strlen(username) + 8);
1785         if (!aaa)
1786                 return -1;
1787
1788         if (text) {
1789                 sprintf(aaa, "SEXP %s|-", username);
1790                 ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
1791         } else {
1792                 sprintf(aaa, "SEXP %s||", username);
1793                 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1794         }
1795         free(aaa);
1796         return ret;
1797 }
1798
1799
1800 /* GEXP */
1801 int CtdlIPCGetInstantMessage(CtdlIPC * ipc, char **listing, char *cret)
1802 {
1803         size_t bytes;
1804
1805         if (!cret)
1806                 return -2;
1807         if (!listing)
1808                 return -2;
1809         if (*listing)
1810                 return -2;
1811
1812         return CtdlIPCGenericCommand(ipc, "GEXP", NULL, 0, listing, &bytes, cret);
1813 }
1814
1815
1816 /* DEXP */
1817 /* mode is 0 = enable, 1 = disable, 2 = status */
1818 int CtdlIPCEnableInstantMessageReceipt(CtdlIPC * ipc, int mode, char *cret)
1819 {
1820         char aaa[64];
1821
1822         if (!cret)
1823                 return -2;
1824
1825         sprintf(aaa, "DEXP %d", mode);
1826         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1827 }
1828
1829
1830 /* EBIO */
1831 int CtdlIPCSetBio(CtdlIPC * ipc, char *bio, char *cret)
1832 {
1833         if (!cret)
1834                 return -2;
1835         if (!bio)
1836                 return -2;
1837
1838         return CtdlIPCGenericCommand(ipc, "EBIO", bio, strlen(bio), NULL, NULL, cret);
1839 }
1840
1841
1842 /* RBIO */
1843 int CtdlIPCGetBio(CtdlIPC * ipc, const char *username, char **listing, char *cret)
1844 {
1845         int ret;
1846         size_t bytes;
1847         char *aaa;
1848
1849         if (!cret)
1850                 return -2;
1851         if (!username)
1852                 return -2;
1853         if (!listing)
1854                 return -2;
1855         if (*listing)
1856                 return -2;
1857
1858         aaa = (char *) malloc(strlen(username) + 6);
1859         if (!aaa)
1860                 return -1;
1861
1862         sprintf(aaa, "RBIO %s", username);
1863         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
1864         free(aaa);
1865         return ret;
1866 }
1867
1868
1869 /* LBIO */
1870 int CtdlIPCListUsersWithBios(CtdlIPC * ipc, char **listing, char *cret)
1871 {
1872         size_t bytes;
1873
1874         if (!cret)
1875                 return -2;
1876         if (!listing)
1877                 return -2;
1878         if (*listing)
1879                 return -2;
1880
1881         return CtdlIPCGenericCommand(ipc, "LBIO", NULL, 0, listing, &bytes, cret);
1882 }
1883
1884
1885 /* STEL */
1886 int CtdlIPCStealthMode(CtdlIPC * ipc, int mode, char *cret)
1887 {
1888         char aaa[64];
1889
1890         if (!cret)
1891                 return -1;
1892
1893         sprintf(aaa, "STEL %d", mode ? 1 : 0);
1894         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1895 }
1896
1897
1898 /* TERM */
1899 int CtdlIPCTerminateSession(CtdlIPC * ipc, int sid, char *cret)
1900 {
1901         char aaa[64];
1902
1903         if (!cret)
1904                 return -1;
1905
1906         sprintf(aaa, "TERM %d", sid);
1907         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1908 }
1909
1910
1911 /* DOWN */
1912 int CtdlIPCTerminateServerNow(CtdlIPC * ipc, char *cret)
1913 {
1914         if (!cret)
1915                 return -1;
1916
1917         return CtdlIPCGenericCommand(ipc, "DOWN", NULL, 0, NULL, NULL, cret);
1918 }
1919
1920
1921 /* SCDN */
1922 int CtdlIPCTerminateServerScheduled(CtdlIPC * ipc, int mode, char *cret)
1923 {
1924         char aaa[16];
1925
1926         if (!cret)
1927                 return -1;
1928
1929         sprintf(aaa, "SCDN %d", mode ? 1 : 0);
1930         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1931 }
1932
1933
1934 /* EMSG */
1935 int CtdlIPCEnterSystemMessage(CtdlIPC * ipc, const char *filename, const char *text, char *cret)
1936 {
1937         int ret;
1938         char *aaa;
1939
1940         if (!cret)
1941                 return -2;
1942         if (!text)
1943                 return -2;
1944         if (!filename)
1945                 return -2;
1946
1947         aaa = (char *) malloc(strlen(filename) + 6);
1948         if (!aaa)
1949                 return -1;
1950
1951         sprintf(aaa, "EMSG %s", filename);
1952         ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
1953         free(aaa);
1954         return ret;
1955 }
1956
1957
1958 /* TIME */
1959 /* This function returns the actual server time reported, or 0 if error */
1960 time_t CtdlIPCServerTime(CtdlIPC * ipc, char *cret)
1961 {
1962         time_t tret;
1963         int ret;
1964
1965         ret = CtdlIPCGenericCommand(ipc, "TIME", NULL, 0, NULL, NULL, cret);
1966         if (ret / 100 == 2) {
1967                 tret = extract_long(cret, 0);
1968         } else {
1969                 tret = 0L;
1970         }
1971         return tret;
1972 }
1973
1974
1975 /* AGUP */
1976 int CtdlIPCAideGetUserParameters(CtdlIPC * ipc, const char *who, struct ctdluser **uret, char *cret)
1977 {
1978         int ret;
1979         char aaa[SIZ];
1980
1981         if (!cret)
1982                 return -2;
1983         if (!uret)
1984                 return -2;
1985         if (!*uret)
1986                 *uret = (struct ctdluser *) calloc(1, sizeof(struct ctdluser));
1987         if (!*uret)
1988                 return -1;
1989
1990         sprintf(aaa, "AGUP %s", who);
1991         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1992
1993         if (ret / 100 == 2) {
1994                 extract_token(uret[0]->fullname, cret, 0, '|', sizeof uret[0]->fullname);
1995                 extract_token(uret[0]->password, cret, 1, '|', sizeof uret[0]->password);
1996                 uret[0]->flags = extract_int(cret, 2);
1997                 uret[0]->timescalled = extract_long(cret, 3);
1998                 uret[0]->posted = extract_long(cret, 4);
1999                 uret[0]->axlevel = extract_int(cret, 5);
2000                 uret[0]->usernum = extract_long(cret, 6);
2001                 uret[0]->lastcall = extract_long(cret, 7);
2002                 uret[0]->USuserpurge = extract_int(cret, 8);
2003         }
2004         return ret;
2005 }
2006
2007
2008 /* ASUP */
2009 int CtdlIPCAideSetUserParameters(CtdlIPC * ipc, const struct ctdluser *uret, char *cret)
2010 {
2011         int ret;
2012         char *aaa;
2013
2014         if (!cret)
2015                 return -2;
2016         if (!uret)
2017                 return -2;
2018
2019         aaa = (char *) malloc(strlen(uret->fullname) + strlen(uret->password) + 84);
2020         if (!aaa)
2021                 return -1;
2022
2023         sprintf(aaa, "ASUP %s|%s|%d|%ld|%ld|%d|%ld|%ld|%d",
2024                 uret->fullname, uret->password, uret->flags, uret->timescalled,
2025                 uret->posted, uret->axlevel, uret->usernum, uret->lastcall, uret->USuserpurge);
2026         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2027         free(aaa);
2028         return ret;
2029 }
2030
2031
2032 /* AGEA */
2033 int CtdlIPCAideGetEmailAddresses(CtdlIPC * ipc, const char *who, char *target_buf, char *cret)
2034 {
2035         int ret;
2036         char aaa[SIZ];
2037         char *emailaddrs = NULL;
2038         size_t emailaddrs_len = 0;
2039
2040         sprintf(aaa, "AGEA %s", who);
2041         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &emailaddrs, &emailaddrs_len, cret);
2042
2043         if (ret / 100 == 1) {
2044                 strcpy(target_buf, emailaddrs);
2045         }
2046
2047         if (emailaddrs != NULL) {
2048                 free(emailaddrs);
2049         }
2050
2051         return ret;
2052 }
2053
2054
2055 /* ASEA */
2056 int CtdlIPCAideSetEmailAddresses(CtdlIPC * ipc, const char *who, char *emailaddrs, char *cret)
2057 {
2058         char aaa[SIZ];
2059         int ret;
2060
2061         if (!who)
2062                 return -2;
2063         if (!emailaddrs)
2064                 return -2;
2065         if (!cret)
2066                 return -2;
2067
2068         sprintf(aaa, "ASEA %s", who);
2069         ret = CtdlIPCGenericCommand(ipc, aaa, emailaddrs, 0, NULL, NULL, cret);
2070         return ret;
2071 }
2072
2073
2074 /* GPEX */
2075 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
2076 /* caller must free the struct ExpirePolicy */
2077 int CtdlIPCGetMessageExpirationPolicy(CtdlIPC * ipc, GPEXWhichPolicy which, struct ExpirePolicy **policy, char *cret)
2078 {
2079         static char *proto[] = {
2080                 strof(roompolicy),
2081                 strof(floorpolicy),
2082                 strof(sitepolicy),
2083                 strof(mailboxespolicy)
2084         };
2085         char cmd[256];
2086         int ret;
2087
2088         if (!cret)
2089                 return -2;
2090         if (!policy)
2091                 return -2;
2092         if (!*policy)
2093                 *policy = (struct ExpirePolicy *) calloc(1, sizeof(struct ExpirePolicy));
2094         if (!*policy)
2095                 return -1;
2096         if (which < 0 || which > 3)
2097                 return -2;
2098
2099         sprintf(cmd, "GPEX %s", proto[which]);
2100         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2101         if (ret / 100 == 2) {
2102                 policy[0]->expire_mode = extract_int(cret, 0);
2103                 policy[0]->expire_value = extract_int(cret, 1);
2104         }
2105         return ret;
2106 }
2107
2108
2109 /* SPEX */
2110 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
2111 /* policy is 0 = inherit, 1 = no purge, 2 = by count, 3 = by age (days) */
2112 int CtdlIPCSetMessageExpirationPolicy(CtdlIPC * ipc, int which, struct ExpirePolicy *policy, char *cret)
2113 {
2114         char aaa[38];
2115         char *whichvals[] = { "room", "floor", "site", "mailboxes" };
2116
2117         if (!cret)
2118                 return -2;
2119         if (which < 0 || which > 3)
2120                 return -2;
2121         if (!policy)
2122                 return -2;
2123         if (policy->expire_mode < 0 || policy->expire_mode > 3)
2124                 return -2;
2125         if (policy->expire_mode >= 2 && policy->expire_value < 1)
2126                 return -2;
2127
2128         sprintf(aaa, "SPEX %s|%d|%d", whichvals[which], policy->expire_mode, policy->expire_value);
2129         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2130 }
2131
2132
2133 /* CONF GET */
2134 int CtdlIPCGetSystemConfig(CtdlIPC * ipc, char **listing, char *cret)
2135 {
2136         size_t bytes;
2137
2138         if (!cret)
2139                 return -2;
2140         if (!listing)
2141                 return -2;
2142         if (*listing)
2143                 return -2;
2144
2145         return CtdlIPCGenericCommand(ipc, "CONF GET", NULL, 0, listing, &bytes, cret);
2146 }
2147
2148
2149 /* CONF SET */
2150 int CtdlIPCSetSystemConfig(CtdlIPC * ipc, const char *listing, char *cret)
2151 {
2152         if (!cret)
2153                 return -2;
2154         if (!listing)
2155                 return -2;
2156
2157         return CtdlIPCGenericCommand(ipc, "CONF SET", listing, strlen(listing), NULL, NULL, cret);
2158 }
2159
2160
2161 /* CONF GETSYS */
2162 int CtdlIPCGetSystemConfigByType(CtdlIPC * ipc, const char *mimetype, char **listing, char *cret)
2163 {
2164         int ret;
2165         char *aaa;
2166         size_t bytes;
2167
2168         if (!cret)
2169                 return -2;
2170         if (!mimetype)
2171                 return -2;
2172         if (!listing)
2173                 return -2;
2174         if (*listing)
2175                 return -2;
2176
2177         aaa = malloc(strlen(mimetype) + 13);
2178         if (!aaa)
2179                 return -1;
2180         sprintf(aaa, "CONF GETSYS|%s", mimetype);
2181         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
2182         free(aaa);
2183         return ret;
2184 }
2185
2186
2187 /* CONF PUTSYS */
2188 int CtdlIPCSetSystemConfigByType(CtdlIPC * ipc, const char *mimetype, const char *listing, char *cret)
2189 {
2190         int ret;
2191         char *aaa;
2192
2193         if (!cret)
2194                 return -2;
2195         if (!mimetype)
2196                 return -2;
2197         if (!listing)
2198                 return -2;
2199
2200         aaa = malloc(strlen(mimetype) + 13);
2201         if (!aaa)
2202                 return -1;
2203         sprintf(aaa, "CONF PUTSYS|%s", mimetype);
2204         ret = CtdlIPCGenericCommand(ipc, aaa, listing, strlen(listing), NULL, NULL, cret);
2205         free(aaa);
2206         return ret;
2207 }
2208
2209
2210 /* GNET */
2211 int CtdlIPCGetRoomNetworkConfig(CtdlIPC * ipc, char **listing, char *cret)
2212 {
2213         size_t bytes;
2214
2215         if (!cret)
2216                 return -2;
2217         if (!listing)
2218                 return -2;
2219         if (*listing)
2220                 return -2;
2221
2222         return CtdlIPCGenericCommand(ipc, "GNET", NULL, 0, listing, &bytes, cret);
2223 }
2224
2225
2226 /* SNET */
2227 int CtdlIPCSetRoomNetworkConfig(CtdlIPC * ipc, const char *listing, char *cret)
2228 {
2229         if (!cret)
2230                 return -2;
2231         if (!listing)
2232                 return -2;
2233
2234         return CtdlIPCGenericCommand(ipc, "SNET", listing, strlen(listing), NULL, NULL, cret);
2235 }
2236
2237
2238 /* REQT */
2239 int CtdlIPCRequestClientLogout(CtdlIPC * ipc, int session, char *cret)
2240 {
2241         char aaa[64];
2242
2243         if (!cret)
2244                 return -2;
2245         if (session < 0)
2246                 return -2;
2247
2248         sprintf(aaa, "REQT %d", session);
2249         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2250 }
2251
2252
2253 /* SEEN */
2254 int CtdlIPCSetMessageSeen(CtdlIPC * ipc, long msgnum, int seen, char *cret)
2255 {
2256         char aaa[27];
2257
2258         if (!cret)
2259                 return -2;
2260         if (msgnum < 0)
2261                 return -2;
2262
2263         sprintf(aaa, "SEEN %ld|%d", msgnum, seen ? 1 : 0);
2264         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2265 }
2266
2267
2268 /* STLS */
2269 int CtdlIPCStartEncryption(CtdlIPC * ipc, char *cret)
2270 {
2271         int a;
2272         int r;
2273         char buf[SIZ];
2274
2275 #ifdef HAVE_OPENSSL
2276         SSL *temp_ssl;
2277
2278         /* New SSL object */
2279         temp_ssl = SSL_new(ssl_ctx);
2280         if (!temp_ssl) {
2281                 error_printf("SSL_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2282                 return -2;
2283         }
2284         /* Pointless flag waving */
2285 #if SSLEAY_VERSION_NUMBER >= 0x0922
2286         SSL_set_session_id_context(temp_ssl, (const unsigned char *) "Citadel SID", 14);
2287 #endif
2288
2289         /* Associate network connection with SSL object */
2290         if (SSL_set_fd(temp_ssl, ipc->sock) < 1) {
2291                 error_printf("SSL_set_fd failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2292                 return -2;
2293         }
2294
2295         if (status_hook != NULL) {
2296                 status_hook("Requesting encryption...\r");
2297         }
2298
2299         /* Ready to start SSL/TLS */
2300         r = CtdlIPCGenericCommand(ipc, "STLS", NULL, 0, NULL, NULL, cret);
2301         if (r / 100 != 2) {
2302                 error_printf("Server can't start TLS: %s\n", buf);
2303                 endtls(temp_ssl);
2304                 return r;
2305         }
2306
2307         /* Do SSL/TLS handshake */
2308         if ((a = SSL_connect(temp_ssl)) < 1) {
2309                 error_printf("SSL_connect failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2310                 endtls(temp_ssl);
2311                 return -2;
2312         }
2313         ipc->ssl = temp_ssl;
2314
2315         error_printf("Encrypting with %s cipher %s\n",
2316                      SSL_CIPHER_get_version(SSL_get_current_cipher(ipc->ssl)), SSL_CIPHER_get_name(SSL_get_current_cipher(ipc->ssl))
2317             );
2318         return r;
2319 #else
2320         return 0;
2321 #endif                          /* HAVE_OPENSSL */
2322 }
2323
2324
2325 #ifdef HAVE_OPENSSL
2326 static void endtls(SSL * ssl)
2327 {
2328         if (ssl) {
2329                 SSL_shutdown(ssl);
2330                 SSL_free(ssl);
2331         }
2332 }
2333 #endif
2334
2335
2336 /* QDIR */
2337 int CtdlIPCDirectoryLookup(CtdlIPC * ipc, const char *address, char *cret)
2338 {
2339         int ret;
2340         char *aaa;
2341
2342         if (!address)
2343                 return -2;
2344         if (!cret)
2345                 return -2;
2346
2347         aaa = (char *) malloc(strlen(address) + 6);
2348         if (!aaa)
2349                 return -1;
2350
2351         sprintf(aaa, "QDIR %s", address);
2352         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2353         free(aaa);
2354         return ret;
2355 }
2356
2357
2358 /* IPGM */
2359 int CtdlIPCInternalProgram(CtdlIPC * ipc, int secret, char *cret)
2360 {
2361         char aaa[30];
2362
2363         if (!cret)
2364                 return -2;
2365         sprintf(aaa, "IPGM %d", secret);
2366         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2367 }
2368
2369
2370
2371
2372 /* ************************************************************************** */
2373 /*           Stuff below this line is not for public consumption            */
2374 /* ************************************************************************** */
2375
2376
2377 /* Read a listing from the server up to 000.  Append to dest if it exists */
2378 char *CtdlIPCReadListing(CtdlIPC * ipc, char *dest)
2379 {
2380         size_t length = 0;
2381         size_t linelength;
2382         char *ret = NULL;
2383         char aaa[SIZ];
2384
2385         ret = dest;
2386         if (ret != NULL) {
2387                 length = strlen(ret);
2388         } else {
2389                 length = 0;
2390         }
2391
2392         while (CtdlIPC_getline(ipc, aaa), strcmp(aaa, "000")) {
2393                 linelength = strlen(aaa);
2394                 ret = (char *) realloc(ret, (size_t) (length + linelength + 2));
2395                 if (ret) {
2396                         strcpy(&ret[length], aaa);
2397                         length += linelength;
2398                         strcpy(&ret[length++], "\n");
2399                 }
2400         }
2401
2402         return (ret);
2403 }
2404
2405
2406 /* Send a listing to the server; generate the ending 000. */
2407 int CtdlIPCSendListing(CtdlIPC * ipc, const char *listing)
2408 {
2409         char *text;
2410
2411         text = (char *) malloc(strlen(listing) + 6);
2412         if (text) {
2413                 strcpy(text, listing);
2414                 while (text[strlen(text) - 1] == '\n')
2415                         text[strlen(text) - 1] = '\0';
2416                 strcat(text, "\n000");
2417                 CtdlIPC_putline(ipc, text);
2418                 free(text);
2419                 text = NULL;
2420         } else {
2421                 /* Malloc failed but we are committed to send */
2422                 /* This may result in extra blanks at the bottom */
2423                 CtdlIPC_putline(ipc, text);
2424                 CtdlIPC_putline(ipc, "000");
2425         }
2426         return 0;
2427 }
2428
2429
2430 /* Partial read of file from server */
2431 size_t CtdlIPCPartialRead(CtdlIPC * ipc, void **buf, size_t offset, size_t bytes, char *cret)
2432 {
2433         size_t len = 0;
2434         char aaa[SIZ];
2435
2436         if (!buf)
2437                 return 0;
2438         if (!cret)
2439                 return 0;
2440         if (bytes < 1)
2441                 return 0;
2442
2443         CtdlIPC_lock(ipc);
2444         sprintf(aaa, "READ %d|%d", (int) offset, (int) bytes);
2445         CtdlIPC_putline(ipc, aaa);
2446         CtdlIPC_getline(ipc, aaa);
2447         if (aaa[0] != '6')
2448                 strcpy(cret, &aaa[4]);
2449         else {
2450                 len = extract_long(&aaa[4], 0);
2451                 *buf = (void *) realloc(*buf, (size_t) (offset + len));
2452                 if (*buf) {
2453                         /* I know what I'm doing */
2454                         serv_read(ipc, ((char *) (*buf) + offset), len);
2455                 } else {
2456                         /* We have to read regardless */
2457                         serv_read(ipc, aaa, len);
2458                         len = 0;
2459                 }
2460         }
2461         CtdlIPC_unlock(ipc);
2462         return len;
2463 }
2464
2465
2466 /* CLOS */
2467 int CtdlIPCEndDownload(CtdlIPC * ipc, char *cret)
2468 {
2469         int ret;
2470
2471         if (!cret)
2472                 return -2;
2473         if (!ipc->downloading)
2474                 return -2;
2475
2476         ret = CtdlIPCGenericCommand(ipc, "CLOS", NULL, 0, NULL, NULL, cret);
2477         if (ret / 100 == 2)
2478                 ipc->downloading = 0;
2479         return ret;
2480 }
2481
2482
2483 /* MSGP */
2484 int CtdlIPCSpecifyPreferredFormats(CtdlIPC * ipc, char *cret, char *formats)
2485 {
2486         int ret;
2487         char cmd[SIZ];
2488
2489         snprintf(cmd, sizeof cmd, "MSGP %s", formats);
2490         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2491         return ret;
2492 }
2493
2494
2495
2496 /* READ */
2497 int CtdlIPCReadDownload(CtdlIPC * ipc, void **buf, size_t bytes, size_t resume, void (*progress_gauge_callback)
2498                          (CtdlIPC *, unsigned long, unsigned long), char *cret)
2499 {
2500         size_t len;
2501
2502         if (!cret)
2503                 return -1;
2504         if (!buf)
2505                 return -1;
2506         if (*buf)
2507                 return -1;
2508         if (!ipc->downloading)
2509                 return -1;
2510
2511         len = resume;
2512         if (progress_gauge_callback)
2513                 progress_gauge_callback(ipc, len, bytes);
2514         while (len < bytes) {
2515                 size_t block;
2516
2517                 block = CtdlIPCPartialRead(ipc, buf, len, 4096, cret);
2518                 if (block == 0) {
2519                         free(*buf);
2520                         return 0;
2521                 }
2522                 len += block;
2523                 if (progress_gauge_callback)
2524                         progress_gauge_callback(ipc, len, bytes);
2525         }
2526         return len;
2527 }
2528
2529 /* READ - pipelined */
2530 int CtdlIPCHighSpeedReadDownload(CtdlIPC * ipc, void **buf, size_t bytes, size_t resume, void (*progress_gauge_callback)
2531                                   (CtdlIPC *, unsigned long, unsigned long), char *cret)
2532 {
2533         size_t len;
2534         int calls;              /* How many calls in the pipeline */
2535         int i;                  /* iterator */
2536         char aaa[4096];
2537
2538         if (!cret)
2539                 return -1;
2540         if (!buf)
2541                 return -1;
2542         if (*buf)
2543                 return -1;
2544         if (!ipc->downloading)
2545                 return -1;
2546
2547         *buf = (void *) realloc(*buf, bytes - resume);
2548         if (!*buf)
2549                 return -1;
2550
2551         len = 0;
2552         CtdlIPC_lock(ipc);
2553         if (progress_gauge_callback)
2554                 progress_gauge_callback(ipc, len, bytes);
2555
2556         /* How many calls will be in the pipeline? */
2557         calls = (bytes - resume) / 4096;
2558         if ((bytes - resume) % 4096)
2559                 calls++;
2560
2561         /* Send all requests at once */
2562         for (i = 0; i < calls; i++) {
2563                 sprintf(aaa, "READ %d|4096", (int) (i * 4096 + resume));
2564                 CtdlIPC_putline(ipc, aaa);
2565         }
2566
2567         /* Receive all responses at once */
2568         for (i = 0; i < calls; i++) {
2569                 CtdlIPC_getline(ipc, aaa);
2570                 if (aaa[0] != '6')
2571                         strcpy(cret, &aaa[4]);
2572                 else {
2573                         len = extract_long(&aaa[4], 0);
2574                         /* I know what I'm doing */
2575                         serv_read(ipc, ((char *) (*buf) + (i * 4096)), len);
2576                 }
2577                 if (progress_gauge_callback)
2578                         progress_gauge_callback(ipc, i * 4096 + len, bytes);
2579         }
2580         CtdlIPC_unlock(ipc);
2581         return len;
2582 }
2583
2584
2585 /* UCLS */
2586 int CtdlIPCEndUpload(CtdlIPC * ipc, int discard, char *cret)
2587 {
2588         int ret;
2589         char cmd[8];
2590
2591         if (!cret)
2592                 return -1;
2593         if (!ipc->uploading)
2594                 return -1;
2595
2596         sprintf(cmd, "UCLS %d", discard ? 0 : 1);
2597         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2598         ipc->uploading = 0;
2599         return ret;
2600 }
2601
2602
2603 /* WRIT */
2604 int CtdlIPCWriteUpload(CtdlIPC * ipc, FILE * uploadFP, void (*progress_gauge_callback)
2605                         (CtdlIPC *, unsigned long, unsigned long), char *cret)
2606 {
2607         int ret = -1;
2608         size_t offset = 0;
2609         size_t bytes;
2610         char aaa[SIZ];
2611         char buf[4096];
2612         FILE *fd = uploadFP;
2613         int ferr;
2614
2615         if (!cret)
2616                 return -1;
2617
2618         fseek(fd, 0L, SEEK_END);
2619         bytes = ftell(fd);
2620         rewind(fd);
2621
2622         if (progress_gauge_callback)
2623                 progress_gauge_callback(ipc, 0, bytes);
2624
2625         while (offset < bytes) {
2626                 size_t to_write;
2627
2628                 /* Read some data in */
2629                 to_write = fread(buf, 1, 4096, fd);
2630                 if (!to_write) {
2631                         if (feof(fd) || ferror(fd))
2632                                 break;
2633                 }
2634                 sprintf(aaa, "WRIT %d", (int) to_write);
2635                 CtdlIPC_putline(ipc, aaa);
2636                 CtdlIPC_getline(ipc, aaa);
2637                 strcpy(cret, &aaa[4]);
2638                 ret = atoi(aaa);
2639                 if (aaa[0] == '7') {
2640                         to_write = extract_long(&aaa[4], 0);
2641
2642                         serv_write(ipc, buf, to_write);
2643                         offset += to_write;
2644                         if (progress_gauge_callback)
2645                                 progress_gauge_callback(ipc, offset, bytes);
2646                         /* Detect short reads and back up if needed */
2647                         /* offset will never be negative anyway */
2648                         fseek(fd, (signed) offset, SEEK_SET);
2649                 } else {
2650                         break;
2651                 }
2652         }
2653         if (progress_gauge_callback)
2654                 progress_gauge_callback(ipc, 1, 1);
2655         ferr = ferror(fd);
2656         fclose(fd);
2657         return (!ferr ? ret : -2);
2658 }
2659
2660
2661 /*
2662  * Generic command method.  This method should handle any server command
2663  * except for CHAT.  It takes the following arguments:
2664  *
2665  * ipc                  The server to speak with
2666  * command              Preformatted command to send to server
2667  * to_send              A text or binary file to send to server
2668  *                      (only sent if server requests it)
2669  * bytes_to_send        The number of bytes in to_send (required if
2670  *                      sending binary, optional if sending listing)
2671  * to_receive           Pointer to a NULL pointer, if the server
2672  *                      sends text or binary we will allocate memory
2673  *                      for the file and stuff it here
2674  * bytes_to_receive     If a file is received, we will store its
2675  *                      byte count here
2676  * proto_response       The protocol response.  Caller must provide
2677  *                      this buffer and ensure that it is at least
2678  *                      128 bytes in length.
2679  *
2680  * This function returns a number equal to the protocol response number,
2681  * -1 if an internal error occurred, -2 if caller provided bad values,
2682  * or 0 - the protocol response number if bad values were found during
2683  * the protocol exchange.
2684  * It stores the protocol response string (minus the number) in 
2685  * protocol_response as described above.  Some commands send additional
2686  * data in this string.
2687  */
2688 int CtdlIPCGenericCommand(CtdlIPC * ipc,
2689                           const char *command, const char *to_send,
2690                           size_t bytes_to_send, char **to_receive, size_t * bytes_to_receive, char *proto_response)
2691 {
2692         char buf[SIZ];
2693         int ret;
2694
2695         if (!command)
2696                 return -2;
2697         if (!proto_response)
2698                 return -2;
2699
2700         CtdlIPC_lock(ipc);
2701         CtdlIPC_putline(ipc, command);
2702         while (1) {
2703                 CtdlIPC_getline(ipc, proto_response);
2704                 if (proto_response[3] == '*')
2705                         instant_msgs = 1;
2706                 ret = atoi(proto_response);
2707                 strcpy(proto_response, &proto_response[4]);
2708                 switch (ret / 100) {
2709                 default:        /* Unknown, punt */
2710                 case 2: /* OK */
2711                 case 3: /* MORE_DATA */
2712                 case 5: /* ERROR */
2713                         /* Don't need to do anything */
2714                         break;
2715                 case 1: /* LISTING_FOLLOWS */
2716                         if (to_receive && !*to_receive && bytes_to_receive) {
2717                                 *to_receive = CtdlIPCReadListing(ipc, NULL);
2718                         } else {        /* Drain */
2719                                 while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000"));
2720                                 ret = -ret;
2721                         }
2722                         break;
2723                 case 4: /* SEND_LISTING */
2724                         if (to_send) {
2725                                 CtdlIPCSendListing(ipc, to_send);
2726                         } else {
2727                                 /* No listing given, fake it */
2728                                 CtdlIPC_putline(ipc, "000");
2729                                 ret = -ret;
2730                         }
2731                         break;
2732                 case 6: /* BINARY_FOLLOWS */
2733                         if (to_receive && !*to_receive && bytes_to_receive) {
2734                                 *bytes_to_receive = extract_long(proto_response, 0);
2735                                 *to_receive = (char *)
2736                                     malloc((size_t) * bytes_to_receive);
2737                                 if (!*to_receive) {
2738                                         ret = -1;
2739                                 } else {
2740                                         serv_read(ipc, *to_receive, *bytes_to_receive);
2741                                 }
2742                         } else {
2743                                 /* Drain */
2744                                 size_t drain;
2745
2746                                 drain = extract_long(proto_response, 0);
2747                                 while (drain > SIZ) {
2748                                         serv_read(ipc, buf, SIZ);
2749                                         drain -= SIZ;
2750                                 }
2751                                 serv_read(ipc, buf, drain);
2752                                 ret = -ret;
2753                         }
2754                         break;
2755                 case 7: /* SEND_BINARY */
2756                         if (to_send && bytes_to_send) {
2757                                 serv_write(ipc, to_send, bytes_to_send);
2758                         } else if (bytes_to_send) {
2759                                 /* Fake it, send nulls */
2760                                 size_t fake;
2761
2762                                 fake = bytes_to_send;
2763                                 memset(buf, '\0', SIZ);
2764                                 while (fake > SIZ) {
2765                                         serv_write(ipc, buf, SIZ);
2766                                         fake -= SIZ;
2767                                 }
2768                                 serv_write(ipc, buf, fake);
2769                                 ret = -ret;
2770                         }       /* else who knows?  DANGER WILL ROBINSON */
2771                         break;
2772                 case 8: /* START_CHAT_MODE */
2773                         if (!strncasecmp(command, "CHAT", 4)) {
2774                                 /* Don't call chatmode with generic! */
2775                                 CtdlIPC_putline(ipc, "/quit");
2776                                 ret = -ret;
2777                         } else {
2778                                 /* In this mode we send then receive listing */
2779                                 if (to_send) {
2780                                         CtdlIPCSendListing(ipc, to_send);
2781                                 } else {
2782                                         /* No listing given, fake it */
2783                                         CtdlIPC_putline(ipc, "000");
2784                                         ret = -ret;
2785                                 }
2786                                 if (to_receive && !*to_receive && bytes_to_receive) {
2787                                         *to_receive = CtdlIPCReadListing(ipc, NULL);
2788                                 } else {        /* Drain */
2789                                         while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000"));
2790                                         ret = -ret;
2791                                 }
2792                         }
2793                         break;
2794                 case 9: /* ASYNC_MSG */
2795                         /* CtdlIPCDoAsync(ret, proto_response); */
2796                         free(CtdlIPCReadListing(ipc, NULL));    /* STUB FIXME */
2797                         break;
2798                 }
2799                 if (ret / 100 != 9)
2800                         break;
2801         }
2802         CtdlIPC_unlock(ipc);
2803         return ret;
2804 }
2805
2806
2807 /*
2808  * Connect to a Citadel on a remote host using a TCP/IP socket
2809  */
2810 static int tcp_connectsock(char *host, char *service)
2811 {
2812         struct in6_addr serveraddr;
2813         struct addrinfo hints;
2814         struct addrinfo *res = NULL;
2815         struct addrinfo *ai = NULL;
2816         int rc = (-1);
2817         int sock = (-1);
2818
2819         if ((host == NULL) || IsEmptyStr(host)) {
2820                 service = DEFAULT_HOST;
2821         }
2822         if ((service == NULL) || IsEmptyStr(service)) {
2823                 service = DEFAULT_PORT;
2824         }
2825
2826         memset(&hints, 0x00, sizeof(hints));
2827         hints.ai_flags = AI_NUMERICSERV;
2828         hints.ai_family = AF_UNSPEC;
2829         hints.ai_socktype = SOCK_STREAM;
2830
2831         /*
2832          * Handle numeric IPv4 and IPv6 addresses
2833          */
2834         rc = inet_pton(AF_INET, host, &serveraddr);
2835         if (rc == 1) {          /* dotted quad */
2836                 hints.ai_family = AF_INET;
2837                 hints.ai_flags |= AI_NUMERICHOST;
2838         } else {
2839                 rc = inet_pton(AF_INET6, host, &serveraddr);
2840                 if (rc == 1) {  /* IPv6 address */
2841                         hints.ai_family = AF_INET6;
2842                         hints.ai_flags |= AI_NUMERICHOST;
2843                 }
2844         }
2845
2846         /* Begin the connection process */
2847
2848         rc = getaddrinfo(host, service, &hints, &res);
2849         if (rc != 0) {
2850                 return (-1);
2851         }
2852
2853         /*
2854          * Try all available addresses until we connect to one or until we run out.
2855          */
2856         for (ai = res; ai != NULL; ai = ai->ai_next) {
2857                 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2858                 if (sock < 0)
2859                         return (-1);
2860
2861                 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
2862                 if (rc >= 0) {
2863                         return (sock);  /* Connected! */
2864                 } else {
2865                         close(sock);    /* Failed.  Close the socket to avoid fd leak! */
2866                 }
2867         }
2868
2869         return (-1);
2870 }
2871
2872
2873
2874
2875
2876 /*
2877  * Connect to a Citadel on the local host using a unix domain socket
2878  */
2879 static int uds_connectsock(int *isLocal, char *sockpath)
2880 {
2881         struct sockaddr_un addr;
2882         int s;
2883
2884         memset(&addr, 0, sizeof(addr));
2885         addr.sun_family = AF_UNIX;
2886         safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
2887
2888         s = socket(AF_UNIX, SOCK_STREAM, 0);
2889         if (s < 0) {
2890                 return -1;
2891         }
2892
2893         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
2894                 close(s);
2895                 return -1;
2896         }
2897
2898         *isLocal = 1;
2899         return s;
2900 }
2901
2902
2903 /*
2904  * input binary data from socket
2905  */
2906 static void serv_read(CtdlIPC * ipc, char *buf, unsigned int bytes)
2907 {
2908         unsigned int len, rlen;
2909
2910 #if defined(HAVE_OPENSSL)
2911         if (ipc->ssl) {
2912                 serv_read_ssl(ipc, buf, bytes);
2913                 return;
2914         }
2915 #endif
2916         len = 0;
2917         while (len < bytes) {
2918                 rlen = read(ipc->sock, &buf[len], bytes - len);
2919                 if (rlen < 1) {
2920                         connection_died(ipc, 0);
2921                         return;
2922                 }
2923                 len += rlen;
2924         }
2925 }
2926
2927
2928 /*
2929  * send binary to server
2930  */
2931 void serv_write(CtdlIPC * ipc, const char *buf, unsigned int nbytes)
2932 {
2933         unsigned int bytes_written = 0;
2934         int retval;
2935
2936 #if defined(HAVE_OPENSSL)
2937         if (ipc->ssl) {
2938                 serv_write_ssl(ipc, buf, nbytes);
2939                 return;
2940         }
2941 #endif
2942         while (bytes_written < nbytes) {
2943                 retval = write(ipc->sock, &buf[bytes_written], nbytes - bytes_written);
2944                 if (retval < 1) {
2945                         connection_died(ipc, 0);
2946                         return;
2947                 }
2948                 bytes_written += retval;
2949         }
2950 }
2951
2952
2953 #ifdef HAVE_OPENSSL
2954 /*
2955  * input binary data from encrypted connection
2956  */
2957 static void serv_read_ssl(CtdlIPC * ipc, char *buf, unsigned int bytes)
2958 {
2959         int len, rlen;
2960         char junk[1];
2961
2962         len = 0;
2963         while (len < bytes) {
2964                 if (SSL_want_read(ipc->ssl)) {
2965                         if ((SSL_write(ipc->ssl, junk, 0)) < 1) {
2966                                 error_printf("SSL_write in serv_read:\n");
2967                                 ERR_print_errors_fp(stderr);
2968                         }
2969                 }
2970                 rlen = SSL_read(ipc->ssl, &buf[len], bytes - len);
2971                 if (rlen < 1) {
2972                         long errval;
2973
2974                         errval = SSL_get_error(ipc->ssl, rlen);
2975                         if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
2976                                 sleep(1);
2977                                 continue;
2978                         }
2979 /***
2980  Not sure why we'd want to handle these error codes any differently,
2981  but this definitely isn't the way to handle them.  Someone must have
2982  naively assumed that we could fall back to unencrypted communications,
2983  but all it does is just recursively blow the stack.
2984                         if (errval == SSL_ERROR_ZERO_RETURN ||
2985                                         errval == SSL_ERROR_SSL) {
2986                                 serv_read(ipc, &buf[len], bytes - len);
2987                                 return;
2988                         }
2989  ***/
2990                         error_printf("SSL_read in serv_read: %s\n", ERR_reason_error_string(ERR_peek_error()));
2991                         connection_died(ipc, 1);
2992                         return;
2993                 }
2994                 len += rlen;
2995         }
2996 }
2997
2998
2999 /*
3000  * send binary to server encrypted
3001  */
3002 static void serv_write_ssl(CtdlIPC * ipc, const char *buf, unsigned int nbytes)
3003 {
3004         unsigned int bytes_written = 0;
3005         int retval;
3006         char junk[1];
3007
3008         while (bytes_written < nbytes) {
3009                 if (SSL_want_write(ipc->ssl)) {
3010                         if ((SSL_read(ipc->ssl, junk, 0)) < 1) {
3011                                 error_printf("SSL_read in serv_write:\n");
3012                                 ERR_print_errors_fp(stderr);
3013                         }
3014                 }
3015                 retval = SSL_write(ipc->ssl, &buf[bytes_written], nbytes - bytes_written);
3016                 if (retval < 1) {
3017                         long errval;
3018
3019                         errval = SSL_get_error(ipc->ssl, retval);
3020                         if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
3021                                 sleep(1);
3022                                 continue;
3023                         }
3024                         if (errval == SSL_ERROR_ZERO_RETURN || errval == SSL_ERROR_SSL) {
3025                                 serv_write(ipc, &buf[bytes_written], nbytes - bytes_written);
3026                                 return;
3027                         }
3028                         error_printf("SSL_write in serv_write: %s\n", ERR_reason_error_string(ERR_peek_error()));
3029                         connection_died(ipc, 1);
3030                         return;
3031                 }
3032                 bytes_written += retval;
3033         }
3034 }
3035
3036
3037
3038
3039 static void CtdlIPC_init_OpenSSL(void)
3040 {
3041         int a;
3042         const SSL_METHOD *ssl_method;
3043         DH *dh;
3044
3045         /* already done init */
3046         if (ssl_ctx) {
3047                 return;
3048         }
3049
3050         /* Get started */
3051         a = 0;
3052         ssl_ctx = NULL;
3053         dh = NULL;
3054         SSL_load_error_strings();
3055         SSLeay_add_ssl_algorithms();
3056
3057         /* Set up the SSL context in which we will oeprate */
3058         ssl_method = SSLv23_client_method();
3059         ssl_ctx = SSL_CTX_new(ssl_method);
3060         if (!ssl_ctx) {
3061                 error_printf("SSL_CTX_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
3062                 return;
3063         }
3064         /* Any reasonable cipher we can get */
3065         if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
3066                 error_printf("No ciphers available for encryption\n");
3067                 return;
3068         }
3069         SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
3070
3071         /* Load DH parameters into the context */
3072         dh = DH_new();
3073         if (!dh) {
3074                 error_printf("Can't allocate a DH object: %s\n", ERR_reason_error_string(ERR_get_error()));
3075                 return;
3076         }
3077
3078         if (!(DH_generate_parameters_ex(dh, 128, DH_GENERATOR_2, 0))) {
3079                 error_printf("Can't generate DH parameters: %s\n", ERR_reason_error_string(ERR_get_error()));
3080                 DH_free(dh);
3081                 return;
3082         }
3083
3084         SSL_CTX_set_tmp_dh(ssl_ctx, dh);
3085         DH_free(dh);
3086 }
3087
3088 #endif                          /* HAVE_OPENSSL */
3089
3090
3091 int ReadNetworkChunk(CtdlIPC * ipc)
3092 {
3093         fd_set read_fd;
3094         int ret = 0;
3095         int err = 0;
3096         struct timeval tv;
3097         size_t n;
3098
3099         tv.tv_sec = 1;
3100         tv.tv_usec = 1000;
3101         /*tries = 0; */
3102         n = 0;
3103         while (1) {
3104                 errno = 0;
3105                 FD_ZERO(&read_fd);
3106                 FD_SET(ipc->sock, &read_fd);
3107                 ret = select(ipc->sock + 1, &read_fd, NULL, NULL, &tv);
3108
3109                 if (ret > 0) {
3110
3111                         *(ipc->BufPtr) = '\0';
3112                         n = recv(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1, 0);
3113                         if (n > 0) {
3114                                 ipc->BufPtr[n] = '\0';
3115                                 ipc->BufUsed += n;
3116                                 return n;
3117                         } else
3118                                 return n;
3119                 } else if (ret < 0) {
3120                         if (!(errno == EINTR || errno == EAGAIN))
3121                                 error_printf("\nselect failed: %d %s\n", err, strerror(err));
3122                         return -1;
3123                 }               /*
3124                                    else {
3125                                    tries ++;
3126                                    if (tries >= 10)
3127                                    n = read(ipc->sock, ipc->BufPtr, ipc->BufSize  - (ipc->BufPtr - ipc->Buf) - 1);
3128                                    if (n > 0) {
3129                                    ipc->BufPtr[n]='\0';
3130                                    ipc->BufUsed += n;
3131                                    return n;
3132                                    }
3133                                    else {
3134                                    connection_died(ipc, 0);
3135                                    return -1;
3136                                    }
3137                                    } */
3138         }
3139 }
3140
3141 /*
3142  * input string from socket - implemented in terms of serv_read()
3143  */
3144 #ifdef CHUNKED_READ
3145
3146 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf)
3147 {
3148         int i, ntries;
3149         char *aptr, *bptr, *aeptr, *beptr;
3150
3151 //      error_printf("---\n");
3152
3153         beptr = buf + SIZ;
3154 #if defined(HAVE_OPENSSL)
3155         if (ipc->ssl) {
3156
3157                 /* Read one character at a time. */
3158                 for (i = 0;; i++) {
3159                         serv_read(ipc, &buf[i], 1);
3160                         if (buf[i] == '\n' || i == (SIZ - 1))
3161                                 break;
3162                 }
3163
3164                 /* If we got a long line, discard characters until the newline. */
3165                 if (i == (SIZ - 1))
3166                         while (buf[i] != '\n')
3167                                 serv_read(ipc, &buf[i], 1);
3168
3169                 /* Strip the trailing newline (and carriage return, if present) */
3170                 if (i >= 0 && buf[i] == 10)
3171                         buf[i--] = 0;
3172                 if (i >= 0 && buf[i] == 13)
3173                         buf[i--] = 0;
3174         } else
3175 #endif
3176         {
3177                 if (ipc->Buf == NULL) {
3178                         ipc->BufSize = SIZ;
3179                         ipc->Buf = (char *) malloc(ipc->BufSize + 10);
3180                         *(ipc->Buf) = '\0';
3181                         ipc->BufPtr = ipc->Buf;
3182                 }
3183
3184                 ntries = 0;
3185 //              while ((ipc->BufUsed == 0)||(ntries++ > 10))
3186                 if (ipc->BufUsed == 0)
3187                         ReadNetworkChunk(ipc);
3188
3189 ////            if (ipc->BufUsed != 0) while (1)
3190                 bptr = buf;
3191
3192                 while (1) {
3193                         aptr = ipc->BufPtr;
3194                         aeptr = ipc->Buf + ipc->BufSize;
3195                         while ((aptr < aeptr) && (bptr < beptr) && (*aptr != '\0') && (*aptr != '\n'))
3196                                 *(bptr++) = *(aptr++);
3197                         if ((*aptr == '\n') && (aptr < aeptr)) {
3198                                 /* Terminate it right, remove the line breaks */
3199                                 while ((aptr < aeptr) && ((*aptr == '\n') || (*aptr == '\r')))
3200                                         aptr++;
3201                                 while ((aptr < aeptr) && (*(aptr + 1) == '\0'))
3202                                         aptr++;
3203                                 *(bptr++) = '\0';
3204 //                              fprintf(stderr, "parsing %d %d %d - %d %d %d %s\n", ipc->BufPtr - ipc->Buf, aptr - ipc->BufPtr, ipc->BufUsed , *aptr, *(aptr-1), *(aptr+1), buf);
3205                                 if ((bptr > buf + 1) && (*(bptr - 1) == '\r'))
3206                                         *(--bptr) = '\0';
3207
3208                                 /* is there more in the buffer we need to read later? */
3209                                 if (ipc->Buf + ipc->BufUsed > aptr) {
3210                                         ipc->BufPtr = aptr;
3211                                 } else {
3212                                         ipc->BufUsed = 0;
3213                                         ipc->BufPtr = ipc->Buf;
3214                                 }
3215 //                              error_printf("----bla6\n");
3216                                 return;
3217
3218                         } /* should we move our read stuf to the bufferstart so we have more space at the end? */
3219                         else if ((ipc->BufPtr != ipc->Buf) && (ipc->BufUsed > (ipc->BufSize - (ipc->BufSize / 4)))) {
3220                                 size_t NewBufSize = ipc->BufSize * 2;
3221                                 int delta = (ipc->BufPtr - ipc->Buf);
3222                                 char *NewBuf;
3223
3224                                 /* if the line would end after our buffer, we should use a bigger buffer. */
3225                                 NewBuf = (char *) malloc(NewBufSize + 10);
3226                                 memcpy(NewBuf, ipc->BufPtr, ipc->BufUsed - delta);
3227                                 free(ipc->Buf);
3228                                 ipc->Buf = ipc->BufPtr = NewBuf;
3229                                 ipc->BufUsed -= delta;
3230                                 ipc->BufSize = NewBufSize;
3231                         }
3232                         if (ReadNetworkChunk(ipc) < 0) {
3233 //                              error_printf("----bla\n");
3234                                 return;
3235                         }
3236                 }
3237 ///             error_printf("----bl45761%s\nipc->BufUsed");
3238         }
3239 //      error_printf("----bla1\n");
3240 }
3241
3242 #else                           /* CHUNKED_READ */
3243
3244 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf)
3245 {
3246         int i;
3247
3248         /* Read one character at a time. */
3249         for (i = 0;; i++) {
3250                 serv_read(ipc, &buf[i], 1);
3251                 if (buf[i] == '\n' || i == (SIZ - 1))
3252                         break;
3253         }
3254
3255         /* If we got a long line, discard characters until the newline. */
3256         if (i == (SIZ - 1))
3257                 while (buf[i] != '\n')
3258                         serv_read(ipc, &buf[i], 1);
3259
3260         /* Strip the trailing newline (and carriage return, if present) */
3261         if (i >= 0 && buf[i] == 10)
3262                 buf[i--] = 0;
3263         if (i >= 0 && buf[i] == 13)
3264                 buf[i--] = 0;
3265 }
3266
3267
3268 #endif                          /* CHUNKED_READ */
3269
3270
3271 void CtdlIPC_chat_recv(CtdlIPC * ipc, char *buf)
3272 {
3273         CtdlIPC_getline(ipc, buf);
3274 }
3275
3276 /*
3277  * send line to server - implemented in terms of serv_write()
3278  */
3279 static void CtdlIPC_putline(CtdlIPC * ipc, const char *buf)
3280 {
3281         char *cmd = NULL;
3282         int len;
3283
3284         len = strlen(buf);
3285         cmd = malloc(len + 2);
3286         if (!cmd) {
3287                 /* This requires no extra memory */
3288                 serv_write(ipc, buf, len);
3289                 serv_write(ipc, "\n", 1);
3290         } else {
3291                 /* This is network-optimized */
3292                 strncpy(cmd, buf, len);
3293                 strcpy(cmd + len, "\n");
3294                 serv_write(ipc, cmd, len + 1);
3295                 free(cmd);
3296         }
3297
3298         ipc->last_command_sent = time(NULL);
3299 }
3300
3301 void CtdlIPC_chat_send(CtdlIPC * ipc, const char *buf)
3302 {
3303         CtdlIPC_putline(ipc, buf);
3304 }
3305
3306
3307 /*
3308  * attach to server
3309  */
3310 CtdlIPC *CtdlIPC_new(int argc, char **argv, char *hostbuf, char *portbuf)
3311 {
3312         int a;
3313         char cithost[SIZ];
3314         char citport[SIZ];
3315         char sockpath[SIZ];
3316         CtdlIPC *ipc;
3317
3318         ipc = malloc(sizeof(struct _CtdlIPC));
3319         if (!ipc) {
3320                 return 0;
3321         }
3322 #if defined(HAVE_OPENSSL)
3323         ipc->ssl = NULL;
3324         CtdlIPC_init_OpenSSL();
3325 #endif
3326         ipc->sock = -1;         /* Not connected */
3327         ipc->isLocal = 0;       /* Not local, of course! */
3328         ipc->downloading = 0;
3329         ipc->uploading = 0;
3330         ipc->last_command_sent = 0L;
3331         ipc->network_status_cb = NULL;
3332         ipc->Buf = NULL;
3333         ipc->BufUsed = 0;
3334         ipc->BufPtr = NULL;
3335
3336         strcpy(cithost, DEFAULT_HOST);  /* default host */
3337         strcpy(citport, DEFAULT_PORT);  /* default port */
3338
3339         /* Allow caller to supply our values */
3340         if (hostbuf && strlen(hostbuf) > 0) {
3341                 strcpy(cithost, hostbuf);
3342         }
3343         if (portbuf && strlen(portbuf) > 0) {
3344                 strcpy(citport, portbuf);
3345         }
3346
3347         /* Read host/port from command line if present */
3348         for (a = 0; a < argc; ++a) {
3349                 if (a == 0) {
3350                         /* do nothing */
3351                 } else if (a == 1) {
3352                         strcpy(cithost, argv[a]);
3353                 } else if (a == 2) {
3354                         strcpy(citport, argv[a]);
3355                 } else {
3356                         error_printf("%s: usage: ", argv[0]);
3357                         error_printf("%s [host] [port] ", argv[0]);
3358                         free(ipc);
3359                         errno = EINVAL;
3360                         return 0;
3361                 }
3362         }
3363
3364         if ((!strcmp(cithost, "localhost")) || (!strcmp(cithost, "127.0.0.1"))) {
3365                 ipc->isLocal = 1;
3366         }
3367
3368         /* If we're using a unix domain socket we can do a bunch of stuff */
3369         if (!strcmp(cithost, UDS)) {
3370                 if (!strcasecmp(citport, DEFAULT_PORT)) {
3371                         snprintf(sockpath, sizeof sockpath, "%s", file_citadel_socket);
3372                 } else {
3373                         snprintf(sockpath, sizeof sockpath, "%s/%s", citport, "citadel.socket");
3374                 }
3375                 printf("[%s]\n", sockpath);
3376                 ipc->sock = uds_connectsock(&(ipc->isLocal), sockpath);
3377                 if (ipc->sock == -1) {
3378                         free(ipc);
3379                         return 0;
3380                 }
3381                 if (hostbuf != NULL)
3382                         strcpy(hostbuf, cithost);
3383                 if (portbuf != NULL)
3384                         strcpy(portbuf, sockpath);
3385                 strcpy(ipc->ip_hostname, "");
3386                 strcpy(ipc->ip_address, "");
3387                 return ipc;
3388         }
3389
3390         printf("[%s:%s]\n", cithost, citport);
3391         ipc->sock = tcp_connectsock(cithost, citport);
3392         if (ipc->sock == -1) {
3393                 free(ipc);
3394                 return 0;
3395         }
3396
3397
3398         /* Learn the actual network identity of the host to which we are connected */
3399
3400         struct sockaddr_in6 clientaddr;
3401         unsigned int addrlen = sizeof(clientaddr);
3402
3403         ipc->ip_hostname[0] = 0;
3404         ipc->ip_address[0] = 0;
3405
3406         getpeername(ipc->sock, (struct sockaddr *) &clientaddr, &addrlen);
3407         getnameinfo((struct sockaddr *) &clientaddr, addrlen, ipc->ip_hostname, sizeof ipc->ip_hostname, NULL, 0, 0);
3408         getnameinfo((struct sockaddr *) &clientaddr, addrlen, ipc->ip_address, sizeof ipc->ip_address, NULL, 0, NI_NUMERICHOST);
3409
3410         /* stuff other things elsewhere */
3411
3412         if (hostbuf != NULL)
3413                 strcpy(hostbuf, cithost);
3414         if (portbuf != NULL)
3415                 strcpy(portbuf, citport);
3416         return ipc;
3417 }
3418
3419
3420 /*
3421  * Disconnect and delete the IPC class (destructor)
3422  */
3423 void CtdlIPC_delete(CtdlIPC * ipc)
3424 {
3425 #ifdef HAVE_OPENSSL
3426         if (ipc->ssl) {
3427                 SSL_shutdown(ipc->ssl);
3428                 SSL_free(ipc->ssl);
3429                 ipc->ssl = NULL;
3430         }
3431 #endif
3432         if (ipc->sock > -1) {
3433                 shutdown(ipc->sock, 2); /* Close it up */
3434                 ipc->sock = -1;
3435         }
3436         if (ipc->Buf != NULL)
3437                 free(ipc->Buf);
3438         ipc->Buf = NULL;
3439         ipc->BufPtr = NULL;
3440         free(ipc);
3441 }
3442
3443
3444 /*
3445  * Disconnect and delete the IPC class (destructor)
3446  * Also NULLs out the pointer
3447  */
3448 void CtdlIPC_delete_ptr(CtdlIPC ** pipc)
3449 {
3450         CtdlIPC_delete(*pipc);
3451         *pipc = NULL;
3452 }
3453
3454
3455 /*
3456  * return the file descriptor of the server socket so we can select() on it.
3457  *
3458  * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3459  * rewritten...
3460  */
3461 int CtdlIPC_getsockfd(CtdlIPC * ipc)
3462 {
3463         return ipc->sock;
3464 }
3465
3466
3467 /*
3468  * return one character
3469  *
3470  * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3471  * rewritten...
3472  */
3473 char CtdlIPC_get(CtdlIPC * ipc)
3474 {
3475         char buf[2];
3476         char ch;
3477
3478         serv_read(ipc, buf, 1);
3479         ch = (int) buf[0];
3480
3481         return (ch);
3482 }