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